8. Toepassingsopdracht – versie 3
We pakken de eerder behandelde oefening (paragrafen 4.3 en 4.4) weer op om deze op te lossen met de code PHP, waarbij gebruik wordt gemaakt van een klasse.
8.1. De scriptboomstructuur

8.2. De uitzondering [ExceptionImpots]
In versie 03 zal een constructor of een methode van een klasse, wanneer er een fout optreedt, de volgende uitzondering van het type [ExceptionImpots] genereren:
Opmerkingen
- regel 4: de klasse [ExceptionImpots] bevindt zich in de naamruimte [Application];
- regel 6: de klasse [ExceptionImpots] is een uitbreiding van de vooraf gedefinieerde klasse in PHP [RuntimeException];
- regel 8: de constructor verwacht twee parameters:
- $message: is de foutmelding die bij de uitzondering hoort;
- $code: is de foutcode die bij de uitzondering hoort. Als deze niet aanwezig is, wordt code 0 gebruikt;
8.3. De klasse [TaxAdminData]
In versie 02 zijn de gegevens van de belastingdienst verzameld:
- eerst in een bestand jSON;
- vervolgens vanuit dit bestand jSON in een associatieve tabel;
In versie 03 bevinden de gegevens van de belastingdienst zich nog steeds in het bestand [taxadmindata.json], maar met andere attribuutnamen:
{
"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 versie 02 werd dit bestand gebruikt om een associatieve tabel te initialiseren. In versie 03 zal het bestand de volgende klasse [TaxAdminData] initialiseren:
<?php
namespace Application;
class TaxAdminData {
// belastingschijven
private $limites;
private $coeffR;
private $coeffN;
// constanten voor de belastingberekening
private $plafondQfDemiPart;
private $plafondRevenusCelibatairePourReduction;
private $plafondRevenusCouplePourReduction;
private $valeurReducDemiPart;
private $plafondDecoteCelibataire;
private $plafondDecoteCouple;
private $plafondImpotCouplePourDecote;
private $plafondImpotCelibatairePourDecote;
private $abattementDixPourcentMax;
private $abattementDixPourcentMin;
// initialisatie
public function setFromJsonFile(string $taxAdminDataFilename): TaxAdminData {
// de inhoud van het belastinggegevensbestand wordt opgehaald
$fileContents = \file_get_contents($taxAdminDataFilename);
$erreur = FALSE;
// fout?
if (!$fileContents) {
// de fout wordt genoteerd
$erreur = TRUE;
$message = "Le fichier des données [$taxAdminDataFilename] n'existe pas";
}
if (!$erreur) {
// de code jSON uit het configuratiebestand wordt opgehaald in een associatieve array
$arrayTaxAdminData = \json_decode($fileContents, true);
// fout?
if ($arrayTaxAdminData === FALSE) {
// de fout wordt genoteerd
$erreur = TRUE;
$message = "Le fichier de données jSON [$taxAdminDataFilename] n'a pu être exploité correctement";
}
}
// fout?
if ($erreur) {
// er wordt een uitzondering gegenereerd
throw new ExceptionImpots($message);
}
// initialisatie van de attributen van de klasse
foreach ($arrayTaxAdminData as $key => $value) {
$this->$key = $value;
}
// er wordt gecontroleerd of alle sleutels zijn geïnitialiseerd
$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é");
}
}
// er wordt gecontroleerd of er alleen reële waarden zijn
foreach ($this as $key => $value) {
// $value moet een reëel getal >=0 zijn of een array van reële getallen >=0
$result = $this->check($value);
// fout?
if ($result->erreur) {
// er wordt een uitzondering gegenereerd
throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
} else {
// de waarde wordt genoteerd
$this->$key = $result->value;
}
}
// het object wordt teruggegeven
return $this;
}
private function check($value): \stdClass {
…
return $result;
}
// toString
public function __toString() {
// JSON-string van het object
return \json_encode(\get_object_vars($this), JSON_UNESCAPED_UNICODE);
}
// getters en 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;
}
…
}
Opmerkingen
- regels 6-20: de attributen die de gelijknamige attributen van het bestand jSON en [taxadmindata.json] zullen bevatten. Dit is een belangrijk punt: de attributen van de klasse [TaxAdminData] zijn identiek aan die van de bestanden jSON en [taxadmindata.json]. Deze eigenschap maakt het schrijven van de code een stuk eenvoudiger;
- de klasse [TaxAdminData] heeft geen constructor. In PHP is het niet mogelijk om meerdere constructors te hebben. Als er één wordt vastgelegd, kan het object dus niet op een andere manier worden geïnitialiseerd. In het vervolg zullen onze klassen geen constructor hebben, maar wel meerdere methoden van het type [setFromQqChose] waarmee het op verschillende manieren kan worden geïnitialiseerd. Het aanmaken van een object van het type [TaxAdminData] gebeurt dan met de uitdrukking:
- regel 23: de methode [setFromJsonFile] initialiseert de attributen van de klasse met die met dezelfde naam in het bestand [$jsonFilename];
- regels 24-42: het bestand jSON wordt gebruikt om de associatieve array [$arrayTaxAdminData] op te bouwen. We zijn deze code al tegengekomen in het script [main.php] van versie 02;
- regels 44-47: als er een fout is opgetreden bij het verwerken van het bestand jSON, wordt er een uitzondering gegenereerd. Deze wordt doorgegeven naar het hoofdscript [main.php];
- regels 48-51: de attributen van de klasse worden geïnitialiseerd. Hier wordt gebruikgemaakt van het feit dat de associatieve array [$arrayTaxAdminData] en de klasse [TaxAdminData] attributen hebben met dezelfde namen als de waarden uit het bestand jSON;
- regels 53-57: er wordt gecontroleerd of alle attributen van de klasse [TaxAdminData] zijn geïnitialiseerd;
- regel 53: de uitdrukking [get_object_vars($this)] levert een associatieve array op waarvan de attributen overeenkomen met die van het object [$this], dus de attributen van de klasse [TaxAdminData]. Hierbij moet worden opgemerkt dat bij de initialisatie in de regels 48-51 mogelijk attributen zijn toegevoegd aan het object [$this]. Als we dus schrijven:
dan wordt het attribuut [x] toegevoegd aan het object [$this], ook al is dit attribuut niet gedeclareerd in de klasse [TaxAdminData]. Wat zeker is, is dat de attributen in de regels 6-20 wel degelijk deel uitmaken van het object [$this], maar dat ze mogelijk niet zijn geïnitialiseerd. Dit is een fout die gemakkelijk gemaakt kan worden; het volstaat om een fout te maken in een attribuutnaam in het bestand [taxadmindata.json];
- regels 54-57: alle attributen van [$this] worden doorlopen en als een ervan niet is geïnitialiseerd, wordt er een uitzondering gegenereerd;
- een attribuut kan met een onjuiste waarde worden geïnitialiseerd. In PHP is het niet mogelijk om een type aan attributen toe te wijzen. De bewerking:
is mogelijk, terwijl het attribuut [$plafondQfDemiPart] een reëel getal zou moeten zijn;
- regels 59-71: er wordt gecontroleerd of elk van de attributen van de klasse een reële, positieve of nulwaarde heeft. Dit wordt gedaan door de functie [check] op regel 76. De parameter [$value] is ofwel een enkele waarde ofwel een array van waarden;
- regel 62: de functie [check] retourneert een object van het type [\stdClass] met twee attributen:
- [erreur]: gelijk aan TRUE als er een fout is opgetreden, anders gelijk aan FALSE;
- [value]: de werkelijke numerieke waarde die overeenkomt met de als parameter doorgegeven parameter [$value], regel 62;
- regel 64: er wordt gecontroleerd of de controle is geslaagd of niet;
- regel 66: als een attribuut geen positief reëel getal of nul is, wordt er een uitzondering gegenereerd;
- regel 69: anders wordt de numerieke waarde opgeslagen;
- regel 73: het object [$this] wordt als resultaat teruggegeven;
De functie [check] is als volgt:
private function check($value): \stdClass {
// $value is ofwel een array van elementen ofwel een enkel element
// er wordt een array aangemaakt
if (!\is_array($value)) {
$tableau = [$value];
} else {
$tableau = $value;
}
// we zetten de array met elementen van onbekend type om in een array van reële getallen
$newTableau = [];
$result = new \stdClass();
// de elementen van de array moeten positieve of nul decimale getallen zijn
$modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
for ($i = 0; $i < count($tableau); $i ++) {
if (preg_match($modèle, $tableau[$i])) {
// we plaatsen de float in newTableau
$newTableau[] = (float) $tableau[$i];
} else {
// de fout wordt genoteerd
$result->erreur = TRUE;
// we stoppen
return $result;
}
}
// het resultaat wordt weergegeven
$result->erreur = FALSE;
if (!\is_array($value)) {
// één enkele waarde
$result->value = $newTableau[0];
} else {
// een lijst met waarden
$result->value = $newTableau;
}
return $result;
}
Opmerkingen
- regel 1: de parameter [$value] is ofwel een array ofwel een enkel element. Bovendien is het type ervan onbekend. De waarde is afkomstig uit het bestand [taxadmindata.json]. Afhankelijk van de waarden die in dit bestand zijn vastgelegd, kunnen de uitgelezen waarden gehele getallen, reële getallen, tekenreeksen of booleaanse waarden zijn. Bijvoorbeeld:
"plafondQfDemiPart": 1551,
"plafondQfDemiPart": 1551.78,
"plafondQfDemiPart": "1551",
"plafondQfDemiPart": "xx",
In geval 1 is de waarde van het type [entier], in geval 2 van het type [réel], in geval 3 van het type [string], dat naar een getal kan worden geconverteerd, in geval 4 van het type [string], dat niet naar een getal kan worden geconverteerd;
- regels 4-8: er wordt een tabel aangemaakt op basis van de parameter [$value] die in regel 1 als parameter is ontvangen;
- regel 10: de array die we gaan vullen met reële getallen;
- regel 11: het resultaat zal een object van het type [\stdClass] zijn;
- regel 13: relationele uitdrukking voor een positief of nul reëel getal;
- regels 14-24: we controleren of alle elementen van de array [$tableau] positieve reële getallen of nul zijn en vullen de array [$newTableau] met deze elementen, omgezet naar het type [float] (regel 17);
- regels 18-23: zodra er een element wordt gedetecteerd dat geen positief reëel getal of nul is, wordt de fout in het resultaat genoteerd en wordt dit resultaat geretourneerd;
- regels 25-34: het geval waarin alle elementen van de array [$tableau] als correct zijn aangemerkt;
- regel 32: de geretourneerde waarde [$result→value] is een array van reële getallen [float] of een enkel reëel getal;
De functie [__toString] in de regels 82-85 retourneert de tekenreeks jSON met de attributen en waarden van het object [$this].
Regels 87-110: de getters en setters van de klasse;
Opmerking: het kan soms wat vervelend zijn om alle get- en set-methoden van een klasse te moeten schrijven, vooral wanneer er veel attributen zijn. NetBeans kan deze, evenals de constructor, automatisch genereren. Hiervoor hoeft u alleen maar de attributen [1] in te voeren:

- in [2], klik met de rechtermuisknop op de plek waar je code wilt invoegen en kies vervolgens de optie [Insert Code];

- in [4], geef aan dat u de constructor wilt genereren;
- in [5], vink alle attributen aan: dit betekent dat u wilt dat de constructor voor elk van de attributen een parameter heeft;
- in [6]: gebruik de stijl van Java-constructors;
- in [7], geef aan dat u expliciet het sleutelwoord [public] vóór de constructor wilt;
- in [8], bevestig;

- in [9] heeft NetBeans de constructor gegenereerd. Het kon echter de typen van de parameters niet invullen omdat het deze niet kent. Voeg ze zelf toe: [10];
Om de getters en setters te genereren, herhaalt u stap 2 tot en met 4, en kiest u in stap 4 voor [Getter and Setter]:

- bij [5] geeft u aan dat u getters en setters voor elk van de attributen wilt;
- geef in [6] aan dat u de getters en setters wilt in de stijl die door Java wordt gebruikt: setAttribut, getAttribut;
- in [7], geef aan dat u wilt dat deze getters en setters openbaar zijn;
- in [8], bevestig;

- in [9], de door NetBeans gegenereerde getters en setters;
Verwijder deze getters en setters en herhaal stap 2 tot en met 7.
- in [8], vink de optie [Fluent Setter] aan die we eerder niet hadden aangevinkt;
Het resultaat is als volgt:

Elke setter eindigt met een bewerking [return $this]. Hierdoor kunnen de attributen als volgt worden geïnitialiseerd:
De waarde van [$data→setLimites($limites)] (regel 32 van de code) is namelijk [$this], dus hier [$data]. We kunnen dus de methode [setCoeffR($coeffR)] van dit object aanroepen, enzovoort, aangezien deze methode op haar beurt ook [$this] oplevert (regel 37 van de code). Deze manier van schrijven van de methoden van een klasse, waarbij methoden die eigenlijk niets zouden moeten retourneren toch het object [$this] retourneren, wordt een ‘fluent-schrijfwijze’ genoemd. Dit maakt het gebruik van deze methoden eenvoudiger.
8.4. De interface [InterfaceImpots]
We definiëren nu de volgende interface [InterfaceImpots] [InterfaceImpots.php]:
<?php
// naamruimte
namespace Application;
interface InterfaceImpots {
// de gegevens van de belastingschijven ophalen waarmee de belasting kan worden berekend
// kan de uitzondering ExceptionImpots activeren
public function getTaxAdminData(): TaxAdminData;
// de interface kan een belasting berekenen
public function calculerImpot(string $marié, int $enfants, int $salaire): array;
// de interface kan gegevens uit tekstbestanden verwerken
// $usersFilename: bestand met gebruikersgegevens in de vorm van burgerlijke staat, aantal kinderen, jaarsalaris
// $resultsFilename: resultatenbestand met de volgende gegevens: burgerlijke staat, aantal kinderen, jaarsalaris, belastingbedrag
// $errorsFilename: bestand met opgetreden fouten
// kan de uitzondering ExceptionImpots activeren
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void;
}
Opmerkingen
- regel 4: de interface wordt in de naamruimte [Application] geplaatst;
- regel 6: de interface voor de berekening van belastingen;
- regel 10: de methode [getTaxAdminData] maakt het mogelijk om gegevens van de belastingdienst op te halen in een object van het type [TaxAdminData] dat we zojuist hebben besproken. Aangezien deze gegevens zich in een bestand, een database of zelfs op het netwerk kunnen bevinden, kan de methode [getTaxAdminData] er niet in slagen de gegevens op te halen. In dat geval zal deze een uitzondering van het type [ExceptionImpots] genereren. Dit is de standaardmethode in objectgeoriënteerd programmeren om een fout te signaleren die is opgetreden in een methode of een constructor;
- regel 13: met de methode [calculerImpot] kan de belasting van een gebruiker worden berekend;
- regel 20: met de methode [executeBatchImpots] kan de belasting van meerdere belastingplichtigen worden berekend;
- [$usersFileName] is de naam van het tekstbestand met de gegevens van de belastingplichtigen;
- [$resultsFileName] is de naam van het tekstbestand met de belastingbedragen voor deze belastingplichtigen;
- [$errorsFileName] is de naam van het tekstbestand met de fouten die zijn opgetreden bij de verwerking van deze bestanden;
De inhoud van het tekstbestand [$usersFileName] zou als volgt kunnen zijn:
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
Merk op dat de regels 5 en 7 onjuiste gegevens bevatten.
De inhoud van het tekstbestand [$resultsFileName] ziet er dan als volgt uit:
en die van het tekstbestand [$errorsFileName] als volgt:
8.5. De klasse [Utilitaires]
We definiëren bovendien een klasse [Utilitaires] in een bestand [Utilitaires.php]:
<?php
// naamruimte
namespace Application;
// een klasse met hulpprogramma's
abstract class Utilitaires {
public static function cutNewLinechar(string $ligne): string {
// de regeleinde-markering van $ligne wordt verwijderd, indien deze bestaat
$longueur = strlen($ligne); // regellengte
while (substr($ligne, $longueur - 1, 1) == "\n" or substr($ligne, $longueur - 1, 1) == "\r") {
$ligne = substr($ligne, 0, $longueur - 1);
$longueur--;
}
// einde – de regel wordt weergegeven
return($ligne);
}
}
Opmerkingen
- regel 4: de klasse [Utilitaires] wordt eveneens in de naamruimte [Exemples] geplaatst;
- regel 9: de methode [cutNewLinechar] verwijdert het eventuele regeleinde-teken uit de tekst die als parameter is doorgegeven. Ze retourneert de aldus gevormde nieuwe regel. Merk op dat dit een statische methode is, wat betekent dat ze wordt aangeroepen in de vorm [Utilitaires::cutNewLineChar];
8.6. De abstracte klasse [AbstractBaseImpots]
De interface [InterfaceImpots] wordt geïmplementeerd door de volgende abstracte klasse [AbstractBaseImpots]: [AbstractBaseImpots.php]:
<?php
// naamruimte
namespace Application;
// definitie van een abstracte klasse AbstractBaseImpots
abstract class AbstractBaseImpots implements InterfaceImpots {
// de gegevens van de belastingdienst
private $taxAdminData = NULL;
// gegevens die nodig zijn voor de berekening van de belasting
abstract function getTaxAdminData(): TaxAdminData;
// berekening van de belasting
// --------------------------------------------------------------------------
public function calculerImpot(string $marié, int $enfants, int $salaire): array {
// $marié: ja, nee
// $enfants: aantal kinderen
// $salaire: jaarsalaris
// $this->taxAdminData: gegevens van de belastingdienst
//
// er wordt gecontroleerd of de gegevens van de belastingdienst aanwezig zijn
if ($this->taxAdminData === NULL) {
$this->taxAdminData = $this->getTaxAdminData();
}
// berekening van de belasting met kinderen
$result1 = $this->calculerImpot2($marié, $enfants, $salaire);
$impot1 = $result1["impôt"];
// berekening van de belasting zonder kinderen
if ($enfants != 0) {
$result2 = $this->calculerImpot2($marié, 0, $salaire);
$impot2 = $result2["impôt"];
// toepassing van het maximum voor de gezinsquotiënt
$plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
if ($enfants < 3) {
// $PLAFOND_QF_DEMI_PART euro voor de eerste twee kinderen
$impot2 = $impot2 - $enfants * $plafonDemiPart;
} else {
// $PLAFOND_QF_DEMI_PART euro voor de eerste twee kinderen, het dubbele voor de volgende
$impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
}
} else {
$impot2 = $impot1;
$result2 = $result1;
}
// we nemen het hoogste belastingtarief
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"];
}
// berekening van een eventuele korting
$décôte = $this->getDecôte($marié, $salaire, $impot);
$impot -= $décôte;
// berekening van een eventuele belastingvermindering
$réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
$impot -= $réduction;
// resultaat
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 {
…
// resultaat
return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
// revenuImposable=jaarsalaris-vrijstelling
// de vrijstelling heeft een minimum en een maximum
private function getRevenuImposable(float $salaire): float {
…
// resultaat
return floor($revenuImposable);
}
// berekent een eventuele korting
private function getDecôte(string $marié, float $salaire, float $impots): float {
…
// resultaat
return ceil($décôte);
}
// berekent een eventuele korting
private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
…
// resultaat
return ceil($réduction);
}
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
…
}
}
Opmerkingen
- regel 4: de klasse [AbstractBaseImpots] bevindt zich in de naamruimte [Application], net als de andere elementen van de applicatie die momenteel wordt geschreven;
- regel 7: de klasse [AbstractBaseImpots] implementeert de interface [InterfaceImpots];
- regel 9: de gegevens van de belastingdienst worden in het attribuut [$taxAdminData] geplaatst;
- regel 12: implementatie van de methode [getTaxAdminData] van de interface. We weten nog niet hoe we deze methode moeten definiëren: we hebben in de paragraaf een voorbeeld gezien waarin de gegevens van de belastingdienst uit een bestand jSON werden gehaald. We zullen nog een ander geval bekijken waarin de gegevens uit een database moeten worden opgehaald. Het is aan de afgeleide klassen om de inhoud van de methode [getTaxAdminData] te definiëren. De twee voorgaande gevallen zullen leiden tot twee afgeleide klassen. De methode [getTaxAdminData] wordt dus als abstract gedeclareerd, waardoor de klasse zelf automatisch abstract wordt (regel 7);
- regels 15-64: de functie voor het berekenen van de belasting die al is behandeld in de paragrafen link en link;
- in versie 02 werden de gegevens van de belastingdienst in een associatieve tabel [$taxAdminData] geplaatst. In versie 03 worden ze in het attribuut [$this→taxAdminData] geplaatst. Het eerste verschil tussen deze twee oplossingen is een verschil in de zichtbaarheid van de belastinggegevens:
- in versie 02 had de associatieve tabel [$taxAdminData] geen globale zichtbaarheid. Deze werd daarom als parameter doorgegeven aan alle functies voor de belastingberekening;
- in versie 03 heeft het attribuut [$this→taxAdminData] een globale zichtbaarheid voor alle methoden van de klasse. Het wordt dus niet als parameter doorgegeven aan alle functies voor de belastingberekening;
- een tweede verschil vloeit voort uit het feit dat in versie 03 functies zijn vervangen door klassenmethoden. Elke methodeaanroep gebeurt nu met een uitdrukking [$this→getMéthode(…)] (regels 27, 31, 57, 60);
- een derde verschil is dat wanneer de methode [calculerImpot] met haar werk begint, zij niet weet of het attribuut [private $taxAdminData] dat zij nodig heeft, is geïnitialiseerd. De constructor van de klasse initialiseert dit namelijk niet. Het is dus aan de methode [calculerImpot] om dit te doen met behulp van de methode [getTaxAdminData] op regel 12. Dit gebeurt op de regels 23-25;
- afgezien van deze verschillen blijven de methoden voor de berekening van de belasting hetzelfde als in de vorige versies;
De functie [executeBatchImpots] is als volgt:
public function executeBatchImpots(string $usersFileName, string $resultsFileName, string $errorsFileName): void {
// er kunnen nogal wat fouten optreden zodra er met bestanden wordt gewerkt
try {
// fouten bij het openen van bestanden
$errors = fopen($errorsFileName, "w");
if (!$errors) {
throw new ExceptionImpots("Impossible de créer le fichier des erreurs [$errorsFileName]", 10);
}
// resultaten bij het openen van het bestand
$results = fopen($resultsFileName, "w");
if (!$results) {
throw new ExceptionImpots("Impossible de créer le fichier des résultats [$resultsFileName]", 11);
}
// het lezen van gebruikersgegevens
// elke regel heeft de vorm: burgerlijke staat, aantal kinderen, jaarsalaris
$data = fopen($usersFileName, "r");
if (!$data) {
throw new ExceptionImpots("Impossible d'ouvrir en lecture les déclarations des contribuables [$usersFileName]", 12);
}
// de huidige regel uit het gebruikersgegevensbestand wordt verwerkt
// die de volgende indeling heeft: burgerlijke staat, aantal kinderen, jaarsalaris
$num = 1; // nummer van de huidige regel
$nbErreurs = 0; // aantal aangetroffen fouten
while ($ligne = fgets($data, 100)) {
// debug
// print "regel nr. " . ($i + 1) . " : " . $ligne;
// eventueel einde-van-regel-teken verwijderen
$ligne = Utilitaires::cutNewLineChar($ligne);
// we halen de 3 velden ‘getrouwd:kinderen:salaris’ op die samen $ligne vormen
list($marié, $enfants, $salaire) = explode(",", $ligne);
// we controleren ze
// de burgerlijke staat moet ‘ja’ of ‘nee’ zijn
$marié = trim(strtolower($marié));
$erreur = ($marié !== "oui" and $marié !== "non");
if (!$erreur) {
// het aantal kinderen moet een geheel getal zijn
$enfants = trim($enfants);
if (!preg_match("/^\s*\d+\s*$/", $enfants)) {
$erreur = TRUE;
} else {
$enfants = (int) $enfants;
}
}
if (!$erreur) {
// het salaris is een geheel getal zonder eurocenten
$salaire = trim($salaire);
if (!preg_match("/^\s*\d+\s*$/", $salaire)) {
$erreur = TRUE;
} else {
$salaire = (int) $salaire;
}
}
// fout?
if ($erreur) {
fputs($errors, "la ligne [$num] du fichier [$usersFileName] est erronée\n");
$nbErreurs++;
} else {
// de belasting wordt berekend
$result = $this->calculerImpot($marié, (int) $enfants, (int) $salaire);
// het resultaat wordt in het resultatenbestand opgenomen
$result = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $result;
fputs($results, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
}
// volgende regel
$num++;
}
// fouten?
if ($nbErreurs > 0) {
throw new ExceptionImpots("Il y a eu des erreurs", 15);
}
} catch (ExceptionImpots $ex) {
// de uitzondering wordt opnieuw geactiveerd
throw $ex;
} finally {
// alle bestanden sluiten
fclose($data);
fclose($results);
fclose($errors);
}
}
Opmerkingen bij de code
- regel 1: de functie ontvangt drie parameters:
- [$usersFileName]: de naam van het tekstbestand met de gegevens van de belastingplichtigen. Elke tekstregel bevat de gegevens van één belastingplichtige in de volgende vorm: burgerlijke staat (ja/nee), aantal kinderen, jaarsalaris:
- (vervolg)
- [$resultsFileName]: de naam van het tekstbestand waarin de resultaten worden opgeslagen. Elke tekstregel heeft de volgende vorm:
- (vervolg)
- [$errorsFileName]: de naam van het tekstbestand met de fouten:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
- regel 3: omdat een aantal bewerkingen een uitzondering kunnen veroorzaken, wordt de volledige code van de methode omgeven door een try / catch / finally;
- regels 3-19: de drie bestanden worden geopend. Zodra het openen mislukt, wordt er een uitzondering gegenereerd;
- regel 24: de regels van het bestand [$data] worden één voor één gelezen met een maximum van 100 tekens (de regels zijn allemaal korter dan 100 tekens);
- regel 28: de statische methode [Utilitaires::cutNewLineChar] wordt gebruikt om eventuele regeleinden te verwijderen;
- regel 30: de drie elementen van de gelezen regel worden opgehaald;
- regels 33-52: de geldigheid van de drie elementen wordt gecontroleerd. Hier wordt geen uitzondering gegenereerd als er een fout is opgetreden, maar wordt het foutbericht weggeschreven naar het tekstbestand [$errors] (regel 55);
- regel 59: als de gelezen regel geldig is, wordt de belasting berekend. Het resultaat wordt weergegeven in de vorm van een associatieve tabel ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
- regel 61: aan het verkregen resultaat worden de sleutels [marié, enfants, salaire] toegevoegd;
- regel 61: het resultaat wordt in het tekstbestand [$results] opgeslagen in de vorm van de tekenreeks jSON van het verkregen resultaat;
- regels 68-70: aan het einde van de verwerking van het bestand [$data] wordt gekeken naar het aantal foutieve regels dat is aangetroffen. Als er ten minste één is, wordt er een uitzondering gegenereerd;
- regels 71-74: de uitzondering die door de code is gegenereerd, wordt opgevangen en onmiddellijk opnieuw gegenereerd (regel 73). Het doel van deze truc is om een clausule [finally] te kunnen hebben op de regels 74-79: ongeacht hoe de uitvoering van de methode eindigt, worden de drie bestanden die door deze code mogelijk zijn geopend, gesloten. Het sluiten van een bestand dat niet is geopend, veroorzaakt geen fout;
8.7. De klasse [ImpotsWithTaxAdminDataInJsonFile]
De abstracte klasse [AbstractBaseImpots] implementeert de methode [getTaxAdminData] van de interface [InterfaceImpots] niet. We moeten deze dus definiëren in een afgeleide klasse. Dat doen we in de volgende afgeleide klasse [ImpotsWithTaxAdminDataInJsonFile]:
<?php
// naamruimte
namespace Application;
// definitie van een klasse ImpotsWithDataInArrays
class ImpotsWithTaxAdminDataInJsonFile extends AbstractBaseImpots {
// een attribuut van het type Data
private $taxAdminData;
// de constructor
public function __construct(string $jsonFileName) {
// $this->taxAdminData wordt geïnitialiseerd op basis van het bestand jSON
$this->taxAdminData = (new TaxAdminData())->setFromJsonFile($jsonFileName);
}
// geeft de gegevens terug waarmee de belasting kan worden berekend
public function getTaxAdminData(): TaxAdminData {
// het attribuut [$this->taxAdminData] wordt teruggegeven
return $this->taxAdminData;
}
}
Opmerkingen
- regel 7: de klasse [ImpotsWithTaxAdminDataInJsonFile] is een afgeleide van de abstracte klasse [AbstractBaseImpots]. Deze klasse moet de methode [getTaxAdminData] definiëren die de bovenliggende klasse niet heeft gedefinieerd;
- regel 9: het attribuut [$taxAdminData] bevat de gegevens van de belastingdienst;
- regels 12-15: de constructor ontvangt als enige parameter de naam van het bestand jSON dat de fiscale gegevens bevat;
- regel 14: er wordt een object van het type [TaxAdminData] aangemaakt en vervolgens geïnitialiseerd. Deze bewerking kan een uitzondering van het type [ExceptionImpots] genereren. Deze uitzondering wordt doorgegeven naar het hoofdscript [main.php];
- regels 18-20: er wordt een inhoud gegeven aan de methode [getTaxAdminData] die de bovenliggende klasse niet had gedefinieerd. Hier volstaat het om het attribuut [$this->taxAdminData] te initialiseren via de constructor;
8.8. Het script [main.php]
Deze klassen en interface worden gebruikt door het volgende script [main.php]:
<?php
// strikte naleving van de gedeclareerde typen van de functieparameters
declare(strict_types = 1);
// naamruimte
namespace Application;
// opname van interfaces en klassen
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 -----------------------------------------------------
// definitie van constanten
const TAXPAYERSDATA_FILENAME = "taxpayersdata.txt";
const RESULTS_FILENAME = "resultats.txt";
const ERRORS_FILENAME = "errors.txt";
const TAXADMINDATA_FILENAME = "taxadmindata.json";
try {
// we maken een object aan ImpotsWithTaxAdminDataInJsonFile
$impots = new ImpotsWithTaxAdminDataInJsonFile(TAXADMINDATA_FILENAME);
// de belastingbatch wordt uitgevoerd
$impots->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// de fout wordt weergegeven
print $ex->getMessage() . "\n";
}
// einde
print "Terminé\n";
exit();
Opmerkingen
- regel 4: er wordt strikte naleving van de typen van de functieparameters opgelegd;
- regel 7: het script [main.php] wordt eveneens in de naamruimte [Application] geplaatst;
- regels 10-15: hier wordt aan de interpreter PHP aangegeven waar de klassen en interfaces zich bevinden die door het script worden gebruikt. Merk op dat we hier geen instructie use hebben gebruikt om de volledige naam van de door het script gebruikte klassen aan te geven. Dit is namelijk niet nodig omdat het script en de klassen zich in dezelfde naamruimte [Application] bevinden;
- regels 18-22: de namen van de tekstbestanden die in het script worden gebruikt;
- regels 24-29: er wordt een object [ImpotsWithTaxAdminDataInJsonFile] aangemaakt en eventuele uitzonderingen worden afgehandeld;
- regel 28: de methode [executeBatchImpots] wordt uitgevoerd, die de belastingen berekent voor alle belastingplichtigen in het bestand [TAXPAYERSDATA_FILENAME]. De resultaten worden opgeslagen in het bestand [RESULTS_FILENAME] en eventuele fouten in het bestand [ERRORS_FILENAME];
- regels 29-32: bij een onherstelbare fout wordt het foutbericht weergegeven;
Resultaten
Met het volgende belastingplichtigenbestand [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
krijgt men het volgende foutenbestand [errors.txt]:
la ligne [5] du fichier [taxpayersdata.txt] est erronée
la ligne [7] du fichier [taxpayersdata.txt] est erronée
en het volgende resultatenbestand [resultats.txt]: