Skip to content

13. Toepassingsopdracht – versie 5

Image

We hebben al verschillende versies van deze oefening geschreven. De laatste versie maakte gebruik van een gelaagde architectuur:

Image

De laag [dao] implementeert een interface [InterfaceDao]. We hebben een klasse gebouwd die deze interface implementeert:

  • [DaoImpotsWithTaxAdminDataInJsonFile], die de fiscale gegevens uit een bestand jSON ophaalde;

We gaan de interface [InterfaceDao] implementeren via een nieuwe klasse [DaoImpotsWithTaxAdminDataInDatabase], die de gegevens van de belastingdienst uit een database MySQL ophaalt.

13.1. Aanmaken van de database [dbimpots-2019]

Volgens het voorbeeld in de paragraaf ‘link’ maken we een database MySQL aan met de naam [dbimpots-2019], waarvan de eigenaar [admimpots] is met het wachtwoord [mdpimpots]:

Image

  • in [1-4] hierboven zien we de database [dbimpots-2019], die op dit moment nog geen tabellen bevat;

Image

  • in [1-5] hierboven zien we dat de gebruiker [admimpots] alle rechten heeft op de database [dbimpots-2019]. Wat we hier niet zien, is dat deze gebruiker het wachtwoord [admimpots] heeft;

We maken nu de tabel [tbtranches] aan, die de belastingschijven zal bevatten:

Image

  • in [1-7] maken we een tabel met de naam [tbtranches] met 4 kolommen;

Image

  • in [3-6] definiëren we een kolom met de naam [id] (3), van het type integer [int] (4), die de primaire sleutel [6] van de tabel zal zijn en automatisch wordt opgehoogd [5] door de functie SGBD. Dit betekent dat MySQL zelf de waarden van de primaire sleutel zal beheren op het moment van invoegingen. Het zal de waarde 1 toekennen aan de primaire sleutel van de eerste invoeging, vervolgens 2 aan de volgende, enzovoort;
  • in [7] biedt de wizard ons andere configuratieopties voor de primaire sleutel aan. Hier volstaan we met het bevestigen van de standaardwaarden in [7];

Image

  • In [8-16] worden de drie andere kolommen van de tabel gedefinieerd:
    • [limites] (8) van het type decimaal getal (9) met 10 cijfers, waarvan 2 decimalen (10), bevat de elementen van kolom 17 van de belastingschijven;
    • [coeffR] (11) van het type decimaal getal (12) met 6 cijfers, waarvan 2 decimalen (13), bevat de gegevens van kolom 18 van de belastingschijven;
    • [coeffN] (14) van het type decimaal getal (15) met 10 cijfers, waarvan 2 decimalen (16), bevat de gegevens uit kolom 19 van de belastingschijven;

Na validatie van deze structuur krijgen we het volgende resultaat:

Image

  • in [5] geeft het sleutelpictogram aan dat kolom [id] de primaire sleutel is. We zien ook dat deze primaire sleutel gehele getallen (6) bevat en dat deze wordt beheerd (auto-increment) door MySQL;

Op dezelfde manier als we de tabel [tbtranches] hebben aangemaakt, maken we de tabel [tbconstantes] aan, die de constanten voor de belastingberekening zal bevatten:

Image

Het is mogelijk om de structuur van de database te exporteren naar een tekstbestand in de vorm van een reeks opdrachten SQL:

Image

De optie [5] exporteert hier alleen de structuur van de database en niet de inhoud ervan. In ons geval bevat de database nog geen inhoud.

Image

Image

Image

De optie [11] genereert het volgende bestand: SQL [dbimpots-2019.sql]:


-- phpMyAdmin SQL Dump
-- versie 4.8.5
-- https://www.phpmyadmin.net/
--
-- Host: localhost:3306
-- Generatietijd: 30 juni 2019 om 01:10 PM
-- Serverversie: 5.7.24
-- PHP-versie: 7.2.11

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

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

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

--
-- Tabelstructuur voor tabel `tbconstantes`
--

DROP TABLE IF EXISTS `tbconstantes`;
CREATE TABLE `tbconstantes` (
  `id` int(11) NOT NULL,
  `plafondQfDemiPart` decimal(10,2) NOT NULL,
  `plafondRevenusCelibatairePourReduction` decimal(10,2) NOT NULL,
  `plafondRevenusCouplePourReduction` decimal(10,2) NOT NULL,
  `valeurReducDemiPart` decimal(10,2) NOT NULL,
  `plafondDecoteCelibataire` decimal(10,2) NOT NULL,
  `plafondDecoteCouple` decimal(10,2) NOT NULL,
  `plafondImpotCelibatairePourDecote` decimal(10,2) NOT NULL,
  `plafondImpotCouplePourDecote` decimal(10,2) NOT NULL,
  `abattementDixPourcentMax` decimal(10,2) NOT NULL,
  `abattementDixPourcentMin` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

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

--
-- Tabelstructuur voor tabel `tbtranches`
--

DROP TABLE IF EXISTS `tbtranches`;
CREATE TABLE `tbtranches` (
  `id` int(11) NOT NULL,
  `limites` decimal(10,2) NOT NULL,
  `coeffR` decimal(10,2) NOT NULL,
  `coeffN` decimal(10,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Indexen voor gedumpte tabellen
--

--
-- Indexen voor tabel `tbconstantes`
--
ALTER TABLE `tbconstantes`
  ADD PRIMARY KEY (`id`);

--
-- Indexen voor tabel `tbtranches`
--
ALTER TABLE `tbtranches`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT voor gedumpte tabellen
--

--
-- AUTO_INCREMENT voor tabel `tbconstantes`
--
ALTER TABLE `tbconstantes`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;

--
-- AUTO_INCREMENT voor tabel `tbtranches`
--
ALTER TABLE `tbtranches`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

U kunt dit bestand SQL gebruiken om de database [dbimpots-2019] opnieuw te genereren als deze is vernietigd of beschadigd. Het is hier niet nodig om de database te verwijderen voordat u deze opnieuw genereert, aangezien het script SQL dit zelf doet:

Image

Image

13.2. Indeling van de code

Om de rol van de verschillende PHP-scripts die we schrijven beter te illustreren, gaan we onze code in mappen indelen:

Image

  • in [1], overzicht van versie 05;
  • in [2], de entiteiten van de applicatie, entiteiten die tussen lagen worden uitgewisseld;
  • in [3], de hulpprogramma’s van de applicatie;
  • in [4], de gegevens die door de applicatie worden gebruikt of geproduceerd. We hebben hier besloten om uitsluitend jSON-bestanden te gebruiken voor tekstbestanden. Deze bieden verschillende voordelen:
    • ze worden door veel tools herkend;
    • deze tools beschikken over syntaxisaccentuering. Bovendien gelden er regels voor de notatie jSON. Wanneer deze regels niet worden nageleefd, geven de tools hiervan een melding. Een fout die bijvoorbeeld moeilijk te detecteren is in een eenvoudig tekstbestand, is het gebruik van hoofdletters en kleine letters O in plaats van nullen. Als deze fout voorkomt, wordt deze gemeld. In de code jSON:

"plafondRevenusCouplePourReduction": 42O74

waar per ongeluk een hoofdletter O is gebruikt in plaats van de nul in [42074], meldt NetBeans de fout:

Image

NetBeans herkent inderdaad de hoofdletter O, waardoor [49O74] als een tekenreeks wordt geïnterpreteerd. Het programma concludeert hieruit dat de syntaxis [4-5] zou moeten zijn: de tekenreeks [47O74] zou tussen aanhalingstekens moeten staan. De ontwikkelaar wordt dus op de fout gewezen en kan deze corrigeren: ofwel de aanhalingstekens toevoegen, ofwel de O vervangen door een nul;

De overige elementen van versie 05 zijn als volgt:

Image

  • in [6], de interfaces en klassen van de laag [Dao];
  • in [7], de interfaces en klassen van de laag [métier];
  • in [8], de belangrijkste scripts van versie 05;

Versie 05 heeft twee afzonderlijke doelstellingen:

  • de database MySQL [dbimpots-2019] vullen met de inhoud van het bestand jSON [Data/txadmindata.json];
  • de belastingberekening implementeren met fiscale gegevens die voortaan afkomstig zijn uit de database MySQL [dbimpots-2019];

We zullen deze twee doelstellingen afzonderlijk behandelen.

13.3. Het vullen van de database [dbimpots-2019]

13.3.1. Doelstelling

Het tekstbestand taxadmindata.json bevat de gegevens van de belastingdienst:


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

Ons doel is om deze gegevens over te zetten naar de eerder aangemaakte database MySQL [dbimpots-2019].

13.3.2. De entiteiten

Image

De entiteit [Database] zal worden gebruikt om de gegevens van het volgende bestand jSON [database.json] in te kapselen:


{
    "dsn": "mysql:host=localhost;dbname=dbimpots-2019",
    "id": "admimpots",
    "pwd": "mdpimpots",
    "tableTranches": "tbtranches",
    "colLimites": "limites",
    "colCoeffR": "coeffr",
    "colCoeffN": "coeffn",
    "tableConstantes": "tbconstantes",
    "colPlafondQfDemiPart": "plafondQfDemiPart",
    "colPlafondRevenusCelibatairePourReduction": "plafondRevenusCelibatairePourReduction",
    "colPlafondRevenusCouplePourReduction": "plafondRevenusCouplePourReduction",
    "colValeurReducDemiPart": "valeurReducDemiPart",
    "colPlafondDecoteCelibataire": "plafondDecoteCelibataire",
    "colPlafondDecoteCouple": "plafondDecoteCouple",
    "colPlafondImpotCelibatairePourDecote": "plafondImpotCelibatairePourDecote",
    "colPlafondImpotCouplePourDecote": "plafondImpotCouplePourDecote",
    "colAbattementDixPourcentMax": "abattementDixPourcentMax",
    "colAbattementDixPourcentMin": "abattementDixPourcentMin"
}

De entiteit [TaxAdminData] zal worden gebruikt om de gegevens van de volgende bestanden jSON en [taxadmindata.json] in te kapselen:


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

De entiteit [TaxPayerData] zal worden gebruikt om de gegevens van de volgende bestanden jSON en [taxpayerdata.json] in te kapselen:


[
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 55555
    },
    {
        "marié": "ouix",
        "enfants": "2x",
        "salaire": "55555x"
    },
    {
        "marié": "oui",
        "enfants": "2",
        "salaire": 50000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 50000
    },
    {
        "marié": "non",
        "enfants": 2,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 5,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 30000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 200000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 20000
    }
]

13.3.2.1. De basisklasse [BaseEntity]

Om de code van de entiteiten te vereenvoudigen, hanteren we de volgende regel: de attributen van een entiteit hebben dezelfde namen als de attributen van het bestand jSON dat de entiteit moet inkapselen. Op basis van deze regel hebben de entiteiten [Database, TaxAdminData, TaxPayerData] gemeenschappelijke kenmerken die kunnen worden samengevat in een bovenliggende klasse. Dit wordt de volgende klasse [BaseEntity]:


<?php

namespace Application;

class BaseEntity {
  // attribuut
  protected $arrayOfAttributes;

  // initialisatie vanuit een bestand jSON
  public function setFromJsonFile(string $jsonFilename) {
    // de inhoud van het belastinggegevensbestand wordt opgehaald
    $fileContents = \file_get_contents($jsonFilename);
    $erreur = FALSE;
    // fout?
    if (!$fileContents) {
      // de fout wordt genoteerd
      $erreur = TRUE;
      $message = "Le fichier des données [$jsonFilename] n'existe pas";
    }
    if (!$erreur) {
      // de code jSON wordt uit het configuratiebestand opgehaald en opgeslagen in een associatieve array
      $this->arrayOfAttributes = \json_decode($fileContents, true);
      // fout?
      if ($this->arrayOfAttributes === FALSE) {
        // de fout wordt genoteerd
        $erreur = TRUE;
        $message = "Le fichier de données jSON [$jsonFilename] 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 ($this->arrayOfAttributes as $key => $value) {
      $this->$key = $value;
    }
    // het object wordt teruggegeven
    return $this;
  }

  public function checkForAllAttributes() {
    // er wordt gecontroleerd of alle sleutels zijn geïnitialiseerd
    foreach (\array_keys($this->arrayOfAttributes) as $key) {
      if ($key !== "arrayOfAttributes" && !isset($this->$key)) {
        throw new ExceptionImpots("L'attribut [$key] de la classe "
          . get_class($this) . " n'a pas été initialisé");
      }
    }
  }

  public function setFromArrayOfAttributes(array $arrayOfAttributes) {
    // bepaalde attributen van de klasse worden geïnitialiseerd
    foreach ($arrayOfAttributes as $key => $value) {
      $this->$key = $value;
    }
    // het object wordt teruggegeven
    return $this;
  }

  // toString
  public function __toString() {
    // attributen van het object
    $arrayOfAttributes = \get_object_vars($this);
    // het attribuut van de bovenliggende klasse wordt verwijderd
    unset($arrayOfAttributes["arrayOfAttributes"]);
    // JSON-string van het object
    return \json_encode($arrayOfAttributes, JSON_UNESCAPED_UNICODE);
  }

  // getter
  public function getArrayOfAttributes() {
    return $this->arrayOfAttributes;
  }

}

Opmerkingen

  • regel 5: de klasse [BaseEntity] is bedoeld om te worden uitgebreid door de klassen [Database, TaxAdminData, TaxPayerData];
  • regel 7: het attribuut [$arrayOfAttributes] is een array die alle attributen bevat van de dochterklasse die [BaseEntity] heeft uitgebreid, evenals hun waarden;
  • regels 9-41: het attribuut [$arrayOfAttributes] wordt geïnitialiseerd op basis van het bestand jSON [$jsonFilename] dat als parameter is doorgegeven. Er wordt een uitzondering van het type [ExceptionImpot] gegenereerd als het bestand jSON niet kon worden gelezen of als het geen geldig jSON-bestand is;
  • regels 36-38: hier staat speciale code die wordt uitgevoerd als deze door een afgeleide klasse wordt uitgevoerd. In dat geval vertegenwoordigt [$this] een instantie van de onderliggende klasse [Database, TaxAdminData, TaxPayerData] en in dat geval initialiseren de regels 36-38 de attributen van deze onderliggende klasse, op voorwaarde dat deze attributen de zichtbaarheid protected (of public) hebben (zie paragraaf link). Er is namelijk gezegd dat de attributen van de entiteiten [Database, TaxAdminData, TaxPayerData] dezelfde waren als de attributen van het bestand jSON dat ze inkapselden. Ten slotte stelt de methode [setFromJsonFile] een dochterklasse in staat om zichzelf te initialiseren op basis van een bestand jSON;
  • regel 40: het object [$this] wordt geretourneerd, dus een instantie van een onderliggende klasse, als de methode [setFromJsonFile] is aangeroepen door een onderliggende klasse;
  • regels 43-51: met de methode [checkForAllAttributes] kan een onderliggende klasse controleren of al haar attributen zijn geïnitialiseerd. Als dat niet het geval is, wordt een uitzondering [ExceptionImpots] gegenereerd. Met deze methode kan de dochterklasse controleren of er in haar bestand jSON geen attributen zijn vergeten;
  • regels 53-60: de methode [setFromArrayOfAttributes] stelt een dochterklasse in staat om al haar attributen of een deel daarvan te initialiseren op basis van een associatieve array waarvan de sleutels dezelfde namen hebben als de attributen van de te initialiseren dochterklasse;
  • regels 63-70: de methode [__toString] maakt het mogelijk om de weergave jSON van een dochterklasse te verkrijgen;

13.3.2.2. De entiteit [Database]

De entiteit [Database] is als volgt:


<?php

namespace Application;

class Database extends BaseEntity {
  // attributen
  protected $dsn;
  protected $id;
  protected $pwd;
  protected $tableTranches;
  protected $colLimites;
  protected $colCoeffR;
  protected $colCoeffN;
  protected $tableConstantes;
  protected $colPlafondQfDemiPart;
  protected $colPlafondRevenusCelibatairePourReduction;
  protected $colPlafondRevenusCouplePourReduction;
  protected $colValeurReducDemiPart;
  protected $colPlafondDecoteCelibataire;
  protected $colPlafondDecoteCouple;
  protected $colPlafondImpotCelibatairePourDecote;
  protected $colPlafondImpotCouplePourDecote;
  protected $colAbattementDixPourcentMax;
  protected $colAbattementDixPourcentMin;



}

De klasse [Database] wordt gebruikt om de gegevens van het volgende bestand jSON [database.json] in te kapselen:


{
    "dsn": "mysql:host=localhost;dbname=dbimpots-2019",
    "id": "admimpots",
    "pwd": "mdpimpots",
    "tableTranches": "tbtranches",
    "colLimites": "limites",
    "colCoeffR": "coeffr",
    "colCoeffN": "coeffn",
    "tableConstantes": "tbconstantes",
    "colPlafondQfDemiPart": "plafondQfDemiPart",
    "colPlafondRevenusCelibatairePourReduction": "plafondRevenusCelibatairePourReduction",
    "colPlafondRevenusCouplePourReduction": "plafondRevenusCouplePourReduction",
    "colValeurReducDemiPart": "valeurReducDemiPart",
    "colPlafondDecoteCelibataire": "plafondDecoteCelibataire",
    "colPlafondDecoteCouple": "plafondDecoteCouple",
    "colPlafondImpotCelibatairePourDecote": "plafondImpotCelibatairePourDecote",
    "colPlafondImpotCouplePourDecote": "plafondImpotCouplePourDecote",
    "colAbattementDixPourcentMax": "abattementDixPourcentMax",
    "colAbattementDixPourcentMin": "abattementDixPourcentMin"
}

De klasse en het bestand jSON hebben dezelfde attributen. Deze beschrijven de kenmerken van de database MySQL [dbimpots-2019]:

dsn
Databasenaam DSN
id
Eigenaar van de database
pwd
Zijn wachtwoord
tableTranches
Naam van de tabel met de belastingschijven
colLimites
colCoeffR
colCoeffN
Kolomnamen van de tabel [tableTranches]
tableConstantes
Naam van de tabel met de constanten voor de belastingberekening
colPlafondQfDemiPart
colPlafondRevenusCelibatairePourReduction
colPlafondRevenusCouplePourReduction
colValeurReducDemiPart
colPlafondDecoteCelibataire
colPlafondDecoteCouple
colPlafondImpotCelibatairePourDecote
colPlafondImpotCouplePourDecote
colAbattementDixPourcentMax
colAbattementDixPourcentMin
Namen van de kolommen in de tabel [tableConstantes] die de constanten voor de belastingberekening bevat

Waarom moeten de tabellen en kolommen worden benoemd, terwijl we hun namen al kennen en deze niet zullen veranderen? Na de tabellen SGBD en MySQL gaan we de tabellen SGBD en PostgreSQL gebruiken om de gegevens van de belastingdienst op te slaan. De namen van de kolommen en tabellen in Postgres volgen echter niet dezelfde regels als die van MySQL. We zullen dus andere namen moeten gebruiken. Dit geldt ook voor andere SGBD-tabellen. Als we code willen hebben die tussen SGBD-tabellen uitwisselbaar is, is het daarom beter om parameters te gebruiken in plaats van vaste namen voor tabellen en kolommen.

Laten we teruggaan naar de code van de klasse [Database]:


<?php

namespace Application;

class Database extends BaseEntity {
  // attributen
  protected $dsn;
  protected $id;
  protected $pwd;
  protected $tableTranches;
  protected $colLimites;
  protected $colCoeffR;
  protected $colCoeffN;
  protected $tableConstantes;
  protected $colPlafondQfDemiPart;
  protected $colPlafondRevenusCelibatairePourReduction;
  protected $colPlafondRevenusCouplePourReduction;
  protected $colValeurReducDemiPart;
  protected $colPlafondDecoteCelibataire;
  protected $colPlafondDecoteCouple;
  protected $colPlafondImpotCelibatairePourDecote;
  protected $colPlafondImpotCouplePourDecote;
  protected $colAbattementDixPourcentMax;
  protected $colAbattementDixPourcentMin;

  // setter
  // initialisatie
  public function setFromJsonFile(string $jsonFilename) {
    // ouder
    parent::setFromJsonFile($jsonFilename);
    // er wordt gecontroleerd of alle attributen zijn geïnitialiseerd
    parent::checkForAllAttributes();
    // het object wordt teruggegeven
    return $this;
  }

  // getters en setters
  public function getDsn() {
    return $this->dsn;
  }



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



}

Opmerkingen

  • regels 7-24: alle attributen van de klasse hebben de zichtbaarheid [protected]. Dit is de voorwaarde om ze te kunnen wijzigen vanuit de bovenliggende klasse [BaseEntity] (zie paragraaf ‘link’);
  • regels 28-35: met de methode [setFromJsonFile] kunnen de attributen van de klasse [Database] worden geïnitialiseerd op basis van de inhoud van een bestand jSON dat als parameter wordt doorgegeven. De attributen van het bestand jSON en die van de klasse [Database] moeten identiek zijn. Als het bestand jSON niet bruikbaar is, wordt er een uitzondering gegenereerd;
  • regel 30: de bovenliggende klasse voert de initialisatie uit;
  • regel 32: de bovenliggende klasse wordt gevraagd te controleren of alle attributen van de klasse [Database] zijn geïnitialiseerd. Als dat niet het geval is, wordt er een uitzondering gegenereerd;
  • regel 34: de zojuist geïnitialiseerde instantie [Database] wordt teruggegeven;
  • regels 37 en verder: de getters en setters van de attributen van de klasse;

13.3.2.3. De entiteit [TaxAdminData]

De entiteit [TaxAdminData] is als volgt:


<?php

namespace Application;

class TaxAdminData extends BaseEntity {
  // belastingtrappen
  protected $limites;
  protected $coeffR;
  protected $coeffN;
  // constanten voor de belastingberekening
  protected $plafondQfDemiPart;
  protected $plafondRevenusCelibatairePourReduction;
  protected $plafondRevenusCouplePourReduction;
  protected $valeurReducDemiPart;
  protected $plafondDecoteCelibataire;
  protected $plafondDecoteCouple;
  protected $plafondImpotCouplePourDecote;
  protected $plafondImpotCelibatairePourDecote;
  protected $abattementDixPourcentMax;
  protected $abattementDixPourcentMin;

  
}

De klasse [TaxAdminData] wordt gebruikt om de gegevens van het volgende bestand jSON [taxadmindata.json] in te kapselen:


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

De klasse en het bestand jSON hebben dezelfde attributen. Deze vertegenwoordigen de gegevens van de belastingdienst. De rest van de code van de klasse [TaxAdminData] is als volgt:


<?php

namespace Application;

class TaxAdminData extends BaseEntity {
  // belastingschijven
  protected $limites;
  protected $coeffR;
  protected $coeffN;
  // constanten voor de belastingberekening
  protected $plafondQfDemiPart;
  protected $plafondRevenusCelibatairePourReduction;
  protected $plafondRevenusCouplePourReduction;
  protected $valeurReducDemiPart;
  protected $plafondDecoteCelibataire;
  protected $plafondDecoteCouple;
  protected $plafondImpotCouplePourDecote;
  protected $plafondImpotCelibatairePourDecote;
  protected $abattementDixPourcentMax;
  protected $abattementDixPourcentMin;

  // initialisatie
  public function setFromJsonFile(string $taxAdminDataFilename) {
    // bovenliggend
    parent::setFromJsonFile($taxAdminDataFilename);
    // er wordt gecontroleerd of alle attributen zijn geïnitialiseerd
    parent::checkForAllAttributes();
    // er wordt gecontroleerd of de waarden van de attributen reële getallen >=0 zijn
    foreach ($this as $key => $value) {
      if ($key !== "arrayOfAttributes") {
        // $value moet een reëel getal >=0 of een array van reële getallen >=0 zijn
        $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;
  }

  protected function check($value): \stdClass {
    // $value is een array van elementen van het type string of een enkel element
    if (!\is_array($value)) {
      $tableau = [$value];
    } else {
      $tableau = $value;
    }
    // de array van strings wordt omgezet 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;
  }

  // getters en setters

}

Opmerkingen

  • regel 23: de methode [setFromJsonFile] dient om de attributen van de klasse [TaxAdminData] te initialiseren op basis van een als parameter doorgegeven bestand jSON. De attributen van het bestand jSON moeten onder dezelfde naam in de klasse voorkomen;
  • regel 25: dit wordt door de bovenliggende klasse gedaan;
  • regel 27: de bovenliggende klasse wordt gevraagd te controleren of alle attributen van de onderliggende klasse zijn geïnitialiseerd;
  • regels 29-42: er wordt lokaal gecontroleerd of alle attributen een reële positieve waarde of nul hebben gekregen. Deze controle is al besproken in de paragraaf ‘link’ van versie 03;

13.3.3. De laag [dao]

Nu kunnen we de code schrijven die de gegevens uit het tekstbestand [taxadmindata.json] overbrengt naar de tabellen [tbtranches, tbconstantes] van de database MySQL [dbimpots-2019]. We hanteren de volgende architectuur:

Image

Image

De laag [dao] implementeert de volgende interface [InterfaceDao4TransferAdminDataFromFile2Database]:


<?php

// naamruimte
namespace Application;

interface InterfaceDao4TransferAdminData2Database {

  public function transferAdminData2Database(): void;
}

Opmerkingen

  • regel 8: de methode [transferAdminData2Database] heeft als taak de gegevens van de belastingdienst in een database op te slaan;

De interface [InterfaceDao4TransferAdminData2Database] wordt geïmplementeerd door de volgende klasse [DaoTransferAdminDataFromJsonFile2Database]:


<?php

// naamruimte
namespace Application;

// definitie van een klasse TransferAdminDataFromFile2DatabaseDao
class DaoTransferAdminDataFromJsonFile2Database implements InterfaceDao4TransferAdminData2Database {
  // attributen van de doeldatabase
  private $database;
  // gegevens van de belastingdienst
  private $taxAdminData;

  // fabrikant
  public function __construct(string $databaseFilename, string $taxAdminDataFilename) {
    // de configuratie van de database wordt opgeslagen
    $this->database = (new Database())->setFromJsonFile($databaseFilename);
    // de belastinggegevens worden opgeslagen
    $this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
  }

  // de gegevens van de belastingschijven uit een tekstbestand overbrengen
  // naar de database
  public function transferAdminData2Database(): void {
    // er wordt in de database gewerkt
    $database = $this->database;
    try {
      // de verbinding met de database wordt geopend
      $connexion = new \PDO($database->getDsn(), $database->getId(), $database->getPwd());
      // we willen dat bij elke fout van SGBD een uitzondering wordt gegenereerd
      $connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
      // we starten een transactie
      $connexion->beginTransaction();
      // we vullen de tabel met belastingschijven
      $this->fillTableTranches($connexion);
      // de tabel met constanten wordt gevuld
      $this->fillTableConstantes($connexion);
      // de transactie wordt succesvol afgerond
      $connexion->commit();
    } catch (\PDOException $ex) {
      // is er een transactie bezig?
      if (isset($connexion) && $connexion->inTransaction()) {
        // de transactie wordt bij een fout beëindigd
        $connexion->rollBack();
      }
      // de uitzondering wordt doorgegeven aan de aanroepende code
      throw new ExceptionImpots($ex->getMessage());
    } finally {
      // de verbinding wordt verbroken
      $connexion = NULL;
    }
  }


  // De tabel met belastingschijven wordt ingevuld
  private function fillTableTranches($connexion): void {

  }

  // de tabel met constanten wordt ingevuld
  private function fillTableConstantes($connexion): void {

  }

}

Opmerkingen

We passen hier toe wat we hebben geleerd in het hoofdstuk over MySQL.

  • regel 7: de klasse [DaoTransferAdminDataFromJsonFile2Database] implementeert de interface [InterfaceDao4TransferAdminData2Database];
  • regel 9: het attribuut [$database] is het object van het type [Database] dat de gegevens van het bestand [database.json] inkapselen;
  • regel 11: het attribuut [$taxAdminData] is het object van het type [TaxAdminData] dat de gegevens van het bestand [taxadmindata.json] omvat;
  • regels 14-19: de constructor ontvangt als parameters de namen van de bestanden [database.json, taxadmindata.json];
  • regel 16: initialisatie van het attribuut [$database];
  • regel 18: initialisatie van het attribuut [$taxAdminData];
  • regel 23: de enige methode van de interface [InterfaceDao4TransferAdminData2Database] wordt geïmplementeerd;
  • regels 26-38: de tabellen [tbtranches, tbconstantes] worden in twee stappen gevuld:
    • regel 34: eerst wordt de tabel [tbtranches] gevuld. Dit gebeurt binnen een transactie (regels 32, 38). De methode [fillTableTranches] (regel 55) genereert een uitzondering zodra er iets misgaat. In dat geval wordt de uitvoering voortgezet met de catch / finally-blokken in de regels 39-50;
    • regel 36: de tabel [tbconstantes] wordt op dezelfde manier gevuld met behulp van de methode [fillTableConstantes] (regel 60);
  • regels 39-47: het geval waarin een uitzondering is gegenereerd door de code;
  • regels 41-44: als er een transactie bestaat, wordt deze geannuleerd;
  • regel 46: er wordt een uitzondering van het type [ExceptionImpots] gegenereerd met de melding van de oorspronkelijke uitzondering, die zelf van een willekeurig type is;
  • regels 47-50: in de clausule [finally] wordt de verbinding verbroken;

De code van de methode [fillTableTranches] is als volgt:


private function fillTableTranches($connexion): void {
    // snelkoppeling naar de database
    $database = $this->database;
    // de gegevens die in de database moeten worden ingevoerd
    $limites = $this->taxAdminData->getLimites();
    $coeffR = $this->taxAdminData->getCoeffR();
    $coeffN = $this->taxAdminData->getCoeffN();
    // de tabel wordt leeggemaakt voor het geval er iets in staat
    $statement = $connexion->prepare("delete from " . $database->getTableTranches());
    $statement->execute();
    // de invoegopdrachten voorbereiden
    $sqlInsert = "insert into {$database->getTableTranches()} "
      . "({$database->getColLimites()}, {$database->getColCoeffR()},"
      . " {$database->getColCoeffN()}) values (:limites, :coeffR, :coeffN)";
    $statement = $connexion->prepare($sqlInsert);
    // de voorbereide opdracht wordt uitgevoerd met de waarden van de belastingschijven
    for ($i = 0; $i < count($limites); $i++) {
      $statement->execute([
        "limites" => $limites[$i],
        "coeffR" => $coeffR[$i],
        "coeffN" => $coeffN[$i]]);
    }
  }

Opmerkingen

  • regel 1: de methode [fillTableTranches] ontvangt als parameter een geopende verbinding. Bovendien is bekend dat er binnen deze verbinding een transactie is gestart;
  • regels 5-7: de waarden die in de tabel moeten worden ingevoegd, worden geleverd door het attribuut [$taxAdminData];
  • regels 9-10: de huidige inhoud van de tabel [tbtranches] wordt verwijderd;
  • regels 12-15: het invoegen van rijen in de tabel wordt voorbereid. Hier worden de kolomnamen gebruikt die worden geleverd door het attribuut [$database];
  • regels 17-22: de in regels 12-15 voorbereide invoeginstructie wordt zo vaak als nodig uitgevoerd;

De code van de methode [fillTableConstantes] is als volgt:


private function fillTableConstantes($connexion): void {
    // snelkoppeling
    $database = $this->database;
    // de tabel wordt leeggemaakt voor het geval er iets in staat
    $statement = $connexion->prepare("delete from {$database->getTableConstantes()}");
    $statement->execute();
    // de invoer wordt voorbereid
    $taxAdminData = $this->taxAdminData;
    $sqlInsert = "insert into {$database->getTableConstantes()}"
      . " ({$database->getColPlafondQfDemiPart()},"
      . " {$database->getColPlafondRevenusCelibatairePourReduction()},"
      . " {$database->getColPlafondRevenusCouplePourReduction()},"
      . " {$database->getColValeurReducDemiPart()},"
      . " {$database->getColPlafondDecoteCelibataire()},"
      . " {$database->getColPlafondDecoteCouple()},"
      . " {$database->getColPlafondImpotCelibatairePourDecote()},"
      . " {$database->getColPlafondImpotCouplePourDecote()},"
      . " {$database->getColAbattementDixPourcentMax()},"
      . " {$database->getColAbattementDixPourcentMin()})"
      . " values ("
      . ":plafondQfDemiPart,"
      . ":plafondRevenusCelibatairePourReduction,"
      . ":plafondRevenusCouplePourReduction,"
      . ":valeurReducDemiPart,"
      . ":plafondDecoteCelibataire,"
      . ":plafondDecoteCouple,"
      . ":plafondImpotCelibatairePourDecote,"
      . ":plafondImpotCouplePourDecote,"
      . ":abattementDixPourcentMax,"
      . ":abattementDixPourcentMin)";
    $statement = $connexion->prepare($sqlInsert);
    // de voorbereide opdracht wordt uitgevoerd
    $statement->execute([
      "plafondQfDemiPart" => $taxAdminData->getPlafondQfDemiPart(),
      "plafondRevenusCelibatairePourReduction" => $taxAdminData->getPlafondRevenusCelibatairePourReduction(),
      "plafondRevenusCouplePourReduction" => $taxAdminData->getPlafondRevenusCouplePourReduction(),
      "valeurReducDemiPart" => $taxAdminData->getValeurReducDemiPart(),
      "plafondDecoteCelibataire" => $taxAdminData->getPlafondDecoteCelibataire(),
      "plafondDecoteCouple" => $taxAdminData->getPlafondDecoteCouple(),
      "plafondImpotCelibatairePourDecote" => $taxAdminData->getPlafondImpotCelibatairePourDecote(),
      "plafondImpotCouplePourDecote" => $taxAdminData->getPlafondImpotCouplePourDecote(),
      "abattementDixPourcentMax" => $taxAdminData->getAbattementDixPourcentMax(),
      "abattementDixPourcentMin" => $taxAdminData->getAbattementDixPourcentMin()
    ]);
  }

Opmerkingen

  • regel 1: de methode [fillTableConstantes] ontvangt een geopende verbinding als parameter. Bovendien is bekend dat er binnen deze verbinding een transactie is gestart;
  • regels 5-6: de tabel [tbconstantes] wordt leeggemaakt;
  • regels 9-31: voorbereiding van de invoegopdracht SQL. Dit is complex omdat er bij deze invoegbewerking 10 kolommen moeten worden geïnitialiseerd en de kolomnamen moeten worden opgehaald uit het attribuut [$database];
  • regels 33-44: uitvoering van de invoegopdracht. Er hoeft slechts één regel te worden ingevoegd. Ook hier wordt de code complex doordat de in te voegen waarden moeten worden opgehaald uit het attribuut [$taxAdminData];

13.3.4. Het hoofdscript

Image

Image

Het hoofdscript maakt gebruik van de laag [dao] om de gegevensoverdracht uit te voeren:


<?php

// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);

// naamruimte
namespace Application;

// foutbeheer door PHP
// ini_set("display_errors", "0");
// opname van interface en klassen
require_once __DIR__ . "/../Entities/BaseEntity.php";
require_once __DIR__ . "/../Entities/TaxAdminData.php";
require_once __DIR__ . "/../Entities/TaxPayerData.php";
require_once __DIR__ . "/../Entities/Database.php";
require_once __DIR__ . "/../Entities/ExceptionImpots.php";
require_once __DIR__ . "/../Utilities/Utilitaires.php";
require_once __DIR__ . "/../Dao/InterfaceDao.php";
require_once __DIR__ . "/../Dao/TraitDao.php";
require_once __DIR__ . "/../Dao/InterfaceDao4TransferAdminData2Database.php";
require_once __DIR__ . "/../Dao/DaoTransferAdminDataFromJsonFile2Database.php";
//
// definitie van constanten
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";

//
try {
  // aanmaken van de laag [dao]
  $dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
  // gegevensoverdracht naar de database
  $dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
  // de fout wordt weergegeven
  print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// einde
print "Terminé\n";
exit;

Opmerkingen

  • regels 12-21: laden van de klassen en interfaces van de applicatie;
  • regels 24-24: de twee bestanden jSON;
  • regel 30: de laag [dao] wordt geïnstantieerd door de twee bestanden jSON aan de constructor door te geven;
  • regel 32: de gegevensoverdracht wordt uitgevoerd;

Wanneer we deze code uitvoeren, krijgen we het volgende resultaat in de database:

Image

In kolom [3] zien we de waarden die door MySQL zijn toegewezen aan de primaire sleutel [id]. De nummering begint bij 1. De bovenstaande schermafbeelding is gemaakt na meerdere uitvoeringen van het script.

Image

Image

13.4. Berekening van de belasting

Image

13.4.1. Architectuur

Versie 04 van de applicatie voor belastingberekening maakte gebruik van een gelaagde architectuur:

Image

De laag [dao] implementeert een interface [InterfaceDao]. We hebben een klasse gebouwd die deze interface implementeert:

  • [DaoImpotsWithTaxAdminDataInJsonFile], die de belastinggegevens uit een bestand jSON ophaalde. Dit was versie 04;

We gaan de interface [InterfaceDao] implementeren via een nieuwe klasse [DaoImpotsWithTaxAdminDataInDatabase], die de gegevens van de belastingdienst uit een database MySQL zal ophalen. De laag [dao] zal, net als voorheen, de resultaten en fouten naar tekstbestanden schrijven en de gegevens van de belastingplichtigen eveneens uit een tekstbestand ophalen. Alleen zullen deze tekstbestanden deze keer jSON-bestanden zijn. Bovendien weten we dat, als we de interface [InterfaceDao] blijven volgen, de laag [métier] niet hoeft te worden aangepast.

Image

13.4.2. De entiteit [TaxPayerData]

Image

De klasse [TaxPayerData] dient om de gegevens uit het volgende bestand jSON [taxpayersdata.json] in een klasse in te kapselen:


[
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 55555
    },
    {
        "marié": "ouix",
        "enfants": "2x",
        "salaire": "55555x"
    },
    {
        "marié": "oui",
        "enfants": "2",
        "salaire": 50000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 50000
    },
    {
        "marié": "non",
        "enfants": 2,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 5,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 30000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 200000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 20000
    }
]

De klasse [TaxPayerData] ziet er als volgt uit:


<?php

// naamruimte
namespace Application;

// de gegevensklasse
class TaxPayerData extends BaseEntity {
  // gegevens die nodig zijn voor de berekening van de belasting van de belastingplichtige
  protected $marié;
  protected $enfants;
  protected $salaire;
  // resultaten van de belastingberekening
  protected $impôt;
  protected $surcôte;
  protected $décôte;
  protected $réduction;
  protected $taux;

  // getters en setters

}

Opmerkingen

  • regel 7: de klasse [TaxPayerData] is een uitbreiding van de klasse [BaseEntity]. Aangezien de methoden van de bovenliggende klasse toereikend zijn, definieert de klasse [TaxPayerData] zelf geen methoden. Ter herinnering: de attributen van de klasse [TaxPayerData] zijn identiek aan die van het bestand jSON [taxpayersdata.json];

13.4.3. De laag [dao]

13.4.3.1. De eigenschap [TraitDao]

De eigenschap [TraitDao] implementeert een deel van de interface [InterfaceDao]. Laten we deze nog eens in herinnering brengen:


<?php

// naamruimte
namespace Application;

interface InterfaceDao {

  // het uitlezen van belastingplichtigengegevens
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;

  // het inlezen van gegevens van de belastingdienst (belastingschijven)
  public function getTaxAdminData(): TaxAdminData;

  // resultaten opslaan
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}

De trait [TraitDao] implementeert de methoden [getTaxPayersData, saveResults] van de interface [InterfaceDao]. Omdat de definitie van de entiteit [TaxPayerData] tussen versie 04 en 05 is gewijzigd, moeten we de code van [TraitDao] herzien:


<?php

// naamruimte
namespace Application;

trait TraitDao {

  // belastingplichtigengegevens inlezen
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
    // de gegevens van de belastingplichtigen worden in een tabel opgehaald
    $baseEntity = new BaseEntity();
    $baseEntity->setFromJsonFile($taxPayersFilename);
    $arrayOfAttributes = $baseEntity->getArrayOfAttributes();
    // tabel met belastingplichtigengegevens
    $taxPayersData = [];
    // foutenarray
    $errors = [];
    // doorlopen van de tabel met attributen van elementen van het type [TaxPayerData]
    $i = 0;
    foreach ($arrayOfAttributes as $attributesOfTaxPayerData) {
      // controle
      $error = $this->check($attributesOfTaxPayerData);
      if (!$error) {
        // een belastingplichtige met +
        $taxPayersData[] = (new TaxPayerData())->setFrOmArrayOfAttributes($attributesOfTaxPayerData);
      } else {
        // een fout van + - noteer het nummer van de ongeldige gegevens
        $error = ["numéro" => $i] + $error;
        $errors[] = $error;
      }
      // volgende
      $i++;
    }
    // de fouten worden opgeslagen in een JSON-bestand
    $string = "";
    foreach ($errors as $error) {
      $string .= \json_encode($error, JSON_UNESCAPED_UNICODE) . "\n";
    }
    $this->saveString($errorsFilename, $string);
    // resultaat van de functie
    return $taxPayersData;
  }

  private function check(array $attributesOfTaxPayerData): array {
    // de gegevens van [$taxPayerData] worden gecontroleerd
    // de lijst met foutieve attributen
    $attributes = [];
    // de burgerlijke staat moet ‘ja’ of ‘nee’ zijn
    $marié = trim(strtolower($attributesOfTaxPayerData["marié"]));
    $erreur = ($marié !== "oui" and $marié !== "non");
    if ($erreur) {
      // de fout wordt genoteerd
      $attributes[] = ["marié" => $marié];
    }
    // het aantal kinderen moet een positief geheel getal of nul zijn
    $enfants = trim($attributesOfTaxPayerData["enfants"]);
    if (!preg_match("/^\d+$/", $enfants)) {
      // de fout wordt genoteerd
      $erreur = TRUE;
      $attributes[] = ["enfants" => $enfants];
    } else {
      $enfants = (int) $enfants;
    }

    // het salaris moet een positief geheel getal of nul zijn (zonder eurocenten)
    $salaire = trim($attributesOfTaxPayerData["salaire"]);
    if (!preg_match("/^\d+$/", $salaire)) {
      // de fout wordt genoteerd
      $erreur = TRUE;
      $attributes[] = ["salaire" => $salaire];
    } else {
      $salaire = (int) $salaire;
    }

    // fout?
    if ($erreur) {
      // terugkeer met fout
      return ["erreurs" => $attributes];
    } else {
      // terugkeer zonder fout
      return [];
    }
  }

  // resultaten opslaan
  public function saveResults(string $resultsFilename, array $taxPayersData): void {
    // de tabel [$taxPayersData] opslaan in het tekstbestand [$resultsFileName]
    // als het tekstbestand [$resultsFileName] niet bestaat, wordt het aangemaakt
    // samenstelling van de tekenreeks jSON met de resultaten
    $string = "[" . implode(",
", $taxPayersData) . "]";
    // opslag van deze tekenreeks
    $this->saveString($resultsFilename, $string);
  }

  // de resultaten van een tabel opslaan in een tekstbestand
  private function saveString(string $fileName, string $data): void {
    // de tekenreeks [$data] opslaan in het tekstbestand [$fileName]
    // als het tekstbestand [$fileName] niet bestaat, wordt het aangemaakt
    if (file_put_contents($fileName, $data) === FALSE) {
      throw new ExceptionImpots("Erreur lors de l'enregistrement de données dans le fichier texte [$fileName]");
    }
  }

}

Opmerkingen

  • [TraitDao] implementeert de methoden [getTaxPayersData] (regel 9) en [saveResults] (regel 86) van de interface [InterfaceDao];
  • regel 9: de methode [getTaxPayersData] ontvangt als parameters:
    • [$taxPayersFilename]: de bestandsnaam jSON van de belastingplichtigengegevens [taxpayersdata.json];
    • [$errorsFilename]: de naam van het bestand jSON met de fouten [errors.json];
  • regels 11-13: de inhoud van het bestand jSON met de gegevens van de belastingplichtigen wordt overgebracht naar een associatieve tabel [$arrayOfAttributes]. Als het bestand jSON onbruikbaar blijkt te zijn, is er een uitzondering [ExceptionImpots] gegenereerd;
  • regel 15: de tabel [$taxPayersData] zal de gegevens van de belastingplichtigen bevatten, ingekapseld in objecten van het type [TaxPayerData];
  • regel 17: de fouten worden verzameld in de tabel [$errors];
  • regels 99-33: opbouw van de tabel [$taxPayersData];
  • regel 22: voordat de gegevens worden ingekapseld in een type [TaxPayerData], worden ze gecontroleerd. De methode [check] retourneert:
    • een array [‘erreurs’=>[…]] met de foutieve attributen als de gegevens onjuist zijn;
    • een lege array als de gegevens correct zijn;
  • regel 25: het geval waarin de gegevens geldig zijn. Er wordt een nieuw object van het type [TaxPayerData] aangemaakt en toegevoegd aan de array [$taxPayersData];
  • regels 26-30: geval waarin de gegevens ongeldig zijn. In de foutmelding wordt het nummer van het foutieve object [TaxPayerData] in het bestand jSON vermeld, zodat de gebruiker het kan terugvinden; vervolgens wordt de fout toegevoegd aan de tabel [$errors];
  • regels 35-39: de opgetreden fouten worden vastgelegd in het bestand jSON, dat als parameter is doorgegeven in [$errorsFilename], regel 9;
  • regel 41: de array met de geconstrueerde [TaxPayerData]-objecten wordt teruggegeven: dat was het doel van de methode;
  • regels 44-83: de privémethode [check] controleert de geldigheid van de parameters [marié, enfants, salaire] van de als parameter doorgegeven array [$attributesOfTaxPayerData] (regel 44). Als er onjuiste attributen zijn, verzamelt de methode deze in de tabel [$attributes] (regels 47, 53, 60, 70) in de vorm van een tabel [‘attribut erroné’=> valeur de l’attribut erroné];
  • regel 78: als er fouten zijn, wordt een array [‘erreurs’=>$attributes] geretourneerd;
  • regel 81: als er geen fouten zijn, wordt een lege foutentabel geretourneerd;
  • regels 86-93: implementatie van de methode [saveResults] van de interface [InterfaceDao];
  • regel 90: de tekenreeks jSON wordt samengesteld om op te slaan in het bestand jSON [$resultsFilename] dat als parameter is doorgegeven in regel 86. De tekenreeks jSON moet worden samengesteld uit een array:
    • elk element van de array wordt van het volgende gescheiden door een komma en een regeleinde;
    • de gehele array staat tussen vierkante haakjes [];
  • regel 92: de tekenreeks jSON wordt opgeslagen in het bestand jSON [$resultsFilename];

13.4.3.2. De klasse [DaoImpotsWithTaxAdminDataInDatabase]

De klasse [DaoImpotsWithTaxAdminDataInDatabase] implementeert de interface [InterfaceDao] als volgt:


<?php

// naamruimte
namespace Application;

// definitie van een klasse ImpotsWithDataInDatabase
class DaoImpotsWithTaxAdminDataInDatabase implements InterfaceDao {
  // gebruik van een eigenschap
  use TraitDao;
  // het object van het type TaxAdminData dat de gegevens van de belastingschijven bevat
  private $taxAdminData;
  // het object van het type [Database] dat de kenmerken van het object van het type BD bevat
  private $database;

  // fabrikant
  public function __construct(string $databaseFilename) {
    // de configuratie jSON van de database wordt opgeslagen
    $this->database = (new Database())->setFromJsonFile($databaseFilename);
    // het attribuut wordt voorbereid
    $this->taxAdminData = new TaxAdminData();
    try {
      // de verbinding met de database wordt geopend
      $connexion = new \PDO(
        $this->database->getDsn(),
        $this->database->getId(),
        $this->database->getPwd());
      // we willen dat bij elke fout van SGBD een uitzondering wordt gegenereerd
      $connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
      // we starten een transactie
      $connexion->beginTransaction();
      // de tabel met belastingschijven wordt gevuld
      $this->getTranches($connexion);
      // de tabel met constanten wordt gevuld
      $this->getConstantes($connexion);
      // de transactie wordt succesvol afgerond
      $connexion->commit();
    } catch (\PDOException $ex) {
      // is er een transactie bezig?
      if (isset($connexion) && $connexion->inTransaction()) {
        // de transactie wordt afgebroken na een fout
        $connexion->rollBack();
      }
      // de uitzondering wordt doorgegeven aan de aanroepende code
      throw new ExceptionImpots($ex->getMessage());
    } finally {
      // de verbinding wordt verbroken
      $connexion = NULL;
    }
  }

  // gegevens uit de database lezen
  private function getTranches($connexion): void {

  }

  // de constantentabel wordt gelezen
  private function getConstantes($connexion): void {

  }

  // de gegevens worden teruggestuurd waarmee de belasting kan worden berekend
  public function getTaxAdminData(): TaxAdminData {
    return $this->taxAdminData;
  }

}

Opmerkingen

  • regel 4: we behouden de naamruimte die al wordt gebruikt voor de andere implementaties van de laag [dao];
  • regel 7: de klasse [DaoImpotsWithTaxAdminDataInDatabase] implementeert de interface [InterfaceDao];
  • regel 9: we importeren de trait [TraitDao]. We weten dat deze trait een deel van de interface implementeert. De enige methode die nog geïmplementeerd moet worden, is de methode [getTaxAdminData] in de regels 62-64. Deze methode beperkt zich tot het teruggeven van het privé-attribuut [taxAdminData] uit regel 11. Hieruit kunnen we afleiden dat de constructor dit attribuut moet initialiseren. Dat is zijn enige taak;
  • regel 16: de constructor ontvangt als enige parameter [$databaseFilename], wat de naam is van het bestand jSON [database.json] dat de database MySQL [dbimpots-2019] definieert ;
  • regel 18: het bestand jSON [$databaseFilename] wordt gebruikt om een object van het type [Database] aan te maken, dat wordt opgebouwd en opgeslagen in het attribuut [$database] van regel 13. Als het bestand jSON niet correct kon worden verwerkt, is er een uitzondering [ExceptionImpots] gegenereerd;
  • regel 20: het object [$this→taxAdminData] wordt aangemaakt, dat door de constructor moet worden geïnitialiseerd;
  • regels 22-26: hier wordt de verbinding met de database geopend. Let op de notatie [\PDO], waarmee de klasse [PDO] van PHP wordt aangeduid. Aangezien we ons namelijk in de naamruimte [Application] bevinden, zou, als we simpelweg [PDO] zouden schrijven, aan deze relatieve naam de huidige naamruimte worden toegevoegd, wat zou resulteren in de klasse [Application\PDO], die niet bestaat;
  • regel 28: bij een fout zal de SGBD een \PDOException starten (regel 37);
  • regel 30: er wordt een transactie gestart. Deze is niet echt nodig, omdat er slechts twee opdrachten SQL worden uitgevoerd, opdrachten die de database niet wijzigen. We doen dit niettemin om ons af te schermen van andere gebruikers van de database;
  • regel 32: het uitlezen van de tabel met belastingschijven [tbtranches] gebeurt via de privémethode [getTranches] uit regel 52;
  • regel 34: het uitlezen van de tabel met berekeningsconstanten [tbconstantes] gebeurt via de privémethode [getConstantes] uit regel 57;
  • regel 36: als deze regel wordt bereikt, is alles goed verlopen. De transactie wordt dus gevalideerd;
  • regels 37-42: als we hier terechtkomen, betekent dit dat er een uitzondering is opgetreden. We annuleren dus de transactie als er een in uitvoering was (regels 39-42). Regel 44: om de uitzonderingen uniform te houden, sturen we het ontvangen uitzonderingsbericht opnieuw, ditmaal in de vorm van een uitzondering van het type [ExceptionImpots];
  • regels 45-48: in alle gevallen (uitzondering of niet) wordt de verbinding verbroken;

De methode [getTranches] is als volgt:


private function getTranches($connexion): void {
     // snelkoppelingen
    $database = $this->database;
    $taxAdminData = $this->taxAdminData;
     // de query wordt voorbereid SELECT
    $statement = $connexion->prepare(
      "select {$database->getColLimites()}," .
      " {$database->getColCoeffR()}," .
      " {$database->getColCoeffN()}" .
      " from {$database->getTableTranches()}");
     // de voorbereide opdracht wordt uitgevoerd met de waarden van de belastingschijven
    $statement->execute();
     // het resultaat wordt geanalyseerd
    $limites = [];
    $coeffR = [];
    $coeffN = [];
     // de drie tabellen worden ingevuld
    while ($tranche = $statement->fetch(\PDO::FETCH_OBJ)) {
      $limites[] = (float) $tranche->{$database->getColLimites()};
      $coeffR[] = (float) $tranche->{$database->getColCoeffR()};
      $coeffN[] = (float) $tranche->{$database->getColCoeffN()};
    }
    // de gegevens worden opgeslagen in het attribuut [$taxAdminData] van de klasse
    $taxAdminData->setFromArrayOfAttributes([
      "limites" => $limites,
      "coeffR" => $coeffR,
      "coeffN" => $coeffN
    ]);
  }

Opmerkingen

  • regel 1: de methode ontvangt als parameter [$connexion], een geopende verbinding waarin een transactie gaande is;
  • regels 2-4: er worden twee snelkoppelingen aangemaakt om te voorkomen dat [$this->database] en [$taxAdminData = $this->taxAdminData] overal in de code moeten worden geschreven. Het gaat hier om kopieën van objectreferenties en niet om kopieën van de objecten zelf;
  • regels 6-10: de opdracht SELECT wordt voorbereid en vervolgens uitgevoerd in regel 12;
  • regels 13-22: het resultaat van SELECT wordt verwerkt. De ontvangen informatie wordt verzameld in drie tabellen [limites, coeffR, coeffN];
  • regels 24-28: de drie tabellen worden gebruikt om het attribuut [$this->taxAdminData] van de klasse te initialiseren;

De privémethode [getConstantes] is als volgt:


private function getConstantes($connexion): void {
    // snelkoppelingen
    $database = $this->database;
    $taxAdminData = $this->taxAdminData;
    // de query SELECT wordt voorbereid
    $select = "select {$database->getColPlafondQfDemiPart()}," .
      "{$database->getColPlafondRevenusCelibatairePourReduction()}," .
      "{$database->getColPlafondRevenusCouplePourReduction()}," . "{$database->getColValeurReducDemiPart()}," .
      "{$database->getColPlafondDecoteCelibataire()}," . "{$database->getColPlafondDecoteCouple()}," .
      "{$database->getColPlafondImpotCelibatairePourDecote()}," . "{$database->getColPlafondImpotCouplePourDecote()}," .
      "{$database->getColAbattementDixPourcentMax()}," . "{$database->getColAbattementDixPourcentMin()}" .
      " from {$database->getTableConstantes()}";
    $statement = $connexion->prepare($select);
    // de voorbereide opdracht wordt uitgevoerd
    $statement->execute();
    // we verwerken het resultaat – hier slechts één regel
    $row = $statement->fetch(\PDO::FETCH_OBJ);
    // we initialiseren het attribuut [$taxAdminData]
    $taxAdminData->setPlafondQfDemiPart($row->{$database->getColPlafondQfDemiPart()});
    $taxAdminData->setPlafondRevenusCelibatairePourReduction(
      $row->{$database->getColPlafondRevenusCelibatairePourReduction()});
    $taxAdminData->setPlafondRevenusCouplePourReduction($row->{$database->getColPlafondRevenusCouplePourReduction()});
    $taxAdminData->setValeurReducDemiPart($row->{$database->getColValeurReducDemiPart()});
    $taxAdminData->setPlafondDecoteCelibataire($row->{$database->getColPlafondDecoteCelibataire()});
    $taxAdminData->setPlafondDecoteCouple($row->{$database->getColPlafondDecoteCouple()});
    $taxAdminData->setPlafondImpotCelibatairePourDecote($row->{$database->getColPlafondImpotCelibatairePourDecote()});
    $taxAdminData->setPlafondImpotCouplePourDecote($row->{$database->getColPlafondImpotCouplePourDecote()});
    $taxAdminData->setAbattementDixPourcentMax($row->{$database->getColAbattementDixPourcentMax()});
    $taxAdminData->setAbattementDixPourcentMin($row->{$database->getColAbattementDixPourcentMin()});
  }

Opmerkingen

  • regel 1: de methode ontvangt als parameter [$connexion], een geopende verbinding waarin een transactie gaande is;
  • regels 2-4: er worden twee snelkoppelingen aangemaakt om te voorkomen dat [$this->database] en [$taxAdminData = $this->taxAdminData] overal in de code moeten worden geschreven. Het gaat hier om kopieën van objectreferenties en niet om kopieën van de objecten zelf;
  • regels 6-15: de opdracht SELECT wordt voorbereid en vervolgens uitgevoerd in regel 15;
  • regels 17-29: het resultaat van SELECT wordt verwerkt. De opgehaalde informatie wordt gebruikt om het attribuut [$this->taxAdminData] van de klasse te initialiseren;

Opmerking: merk op dat de klasse niet afhankelijk is van de SGBD en MySQL. Het is de aanroepende code die de SGBD vaststelt die via de DSN uit de database wordt gebruikt.

13.4.4. De laag [métier]

Image

  • we hebben zojuist de laag [dao] (3) geïmplementeerd;
  • omdat we de interface [InterfaceDao] hebben gerespecteerd, kan de laag [métier] (2) in theorie ongewijzigd blijven. We hebben echter niet alleen de laag [dao] aangepast. We hebben ook de entiteiten aangepast die door alle lagen worden gedeeld;

De laag [métier] implementeert de volgende interface [InterfaceMetier]:


<?php

// naamruimte
namespace Application;

interface InterfaceMetier {

  // berekening van de belastingen van een belastingplichtige
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;

  // berekening van belastingen in batchmodus
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}
  • regel 12: de methode [executeBatchImpots] maakt nu gebruik van het bestand jSON [$taxPayersFileName], terwijl dit in versie 04 een eenvoudig tekstbestand was.;

In versie 04 zag de methode [executeBatchImpots] er als volgt uit:


public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // uitzonderingen uit de laag [dao] worden doorgegeven
    // de belastingplichtigengegevens worden opgehaald
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // resultatenoverzicht
    $results = [];
    // deze worden verwerkt
    foreach ($taxPayersData as $taxPayerData) {
      // de belasting wordt berekend
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // we vullen het aan [$taxPayerData]
      $taxPayerData->setMontant($result["impôt"]);
      $taxPayerData->setDécôte($result["décôte"]);
      $taxPayerData->setSurCôte($result["surcôte"]);
      $taxPayerData->setTaux($result["taux"]);
      $taxPayerData->setRéduction($result["réduction"]);
      // het resultaat wordt in de resultatenlijst geplaatst
      $results [] = $taxPayerData;
    }
    // de resultaten worden opgeslagen
    $this->dao->saveResults($resultsFileName, $results);
  }
  • regel 15 is nu onjuist. In de nieuwe definitie van de klasse [TaxPayerData] bestaat de methode [setMontant] niet meer;

In versie 05 zal de methode [executeBatchImpots] als volgt luiden:


public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // we laten de uitzonderingen die afkomstig zijn van de laag [dao] door
    // de belastingplichtigengegevens worden opgehaald
    $taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // resultatenoverzicht
    $results = [];
    // deze worden verwerkt
    foreach ($taxPayersData as $taxPayerData) {
      // de belasting wordt berekend
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // we vullen [$taxPayerData] aan
      $taxPayerData->setFromArrayOfAttributes($result);
      // het resultaat wordt in de resultatenlijst geplaatst
      $results [] = $taxPayerData;
    }
    // de resultaten worden opgeslagen
    $this->dao->saveResults($resultsFileName, $results);
  }

Opmerkingen

  • regel 15: in plaats van de afzonderlijke setters van de klasse [TaxPayerData] te gebruiken, wordt de globale setter [setFromArrayOfAttributes] gebruikt;
  • de rest van de code hoeft niet te worden gewijzigd;

13.4.5. Het hoofdscript

Image

  • we hebben zojuist de lagen [dao] (3) en [métier] (2) geïmplementeerd;
  • nu moeten we nog het hoofdscript (1) schrijven;

Het hoofdscript is vergelijkbaar met dat van versie 04:


<?php

// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);

// naamruimte
namespace Application;

// foutbeheer door PHP
//ini_set("display_errors", "0");
// opname van interface en klassen
require_once __DIR__ . "/../Entities/BaseEntity.php";
require_once __DIR__ . "/../Entities/TaxAdminData.php";
require_once __DIR__ . "/../Entities/TaxPayerData.php";
require_once __DIR__ . "/../Entities/Database.php";
require_once __DIR__ . "/../Entities/ExceptionImpots.php";
require_once __DIR__ . "/../Utilities/Utilitaires.php";
require_once __DIR__ . "/../Dao/InterfaceDao.php";
require_once __DIR__ . "/../Dao/TraitDao.php";
require_once __DIR__ . "/../Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once __DIR__ . "/../Métier/InterfaceMetier.php";
require_once __DIR__ . "/../Métier/Metier.php";
//
// definitie van constanten
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
const RESULTS_FILENAME = "../Data/resultats.json";
const ERRORS_FILENAME = "../Data/errors.json";
const TAXPAYERSDATA_FILENAME = "../Data/taxpayersdata.json";

try {
  // aanmaken van de laag [dao]
  $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
  // aanmaken van de laag [métier]
  $métier = new Metier($dao);
  // berekening van de belasting in batchmodus
  $métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
  // de fout wordt weergegeven
  print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// einde
print "Terminé\n";
exit;


Opmerkingen

  • regels 12-22: laden van alle bestanden van versie 05;
  • regels 25-29: de namen van de verschillende jSON-bestanden van de applicatie;
  • regel 33: opbouw van de laag [dao];
  • regel 35: aanmaken van de laag [métier];
  • regel 37: aanroep van de methode [executeBatchImpots] van de laag [métier];

Resultaten

De applicatie genereert twee bestanden jSON:

  • [resultats.json]: de resultaten van de verschillende belastingberekeningen;
  • [errors.json]: waarin de fouten worden gemeld die zijn aangetroffen in de bestanden jSON en [taxpayersdata.json];

Het bestand [errors.json] ziet er als volgt uit:


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

Dit betekent dat in [taxpayersdata.json] het element nr. 1 van de tabel met belastingplichtigen onjuist is. Het bestand [taxpayersdata.json] zag er als volgt uit:


[
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 55555
    },
    {
        "marié": "ouix",
        "enfants": "2x",
        "salaire": "55555x"
    },
    {
        "marié": "oui",
        "enfants": "2",
        "salaire": 50000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 50000
    },
    {
        "marié": "non",
        "enfants": 2,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 5,
        "salaire": 100000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 100000
    },
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 30000
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 200000
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 20000
    }
]

Het resultatenbestand [resultats.json] ziet er als volgt uit:


[
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 55555,
        "impôt": 2814,
        "surcôte": 0,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.14
    },
    {
        "marié": "oui",
        "enfants": "2",
        "salaire": 50000,
        "impôt": 1384,
        "surcôte": 0,
        "décôte": 384,
        "réduction": 347,
        "taux": 0.14
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 50000,
        "impôt": 0,
        "surcôte": 0,
        "décôte": 720,
        "réduction": 0,
        "taux": 0.14
    },
    {
        "marié": "non",
        "enfants": 2,
        "salaire": 100000,
        "impôt": 19884,
        "surcôte": 4480,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.41
    },
    {
        "marié": "non",
        "enfants": 3,
        "salaire": 100000,
        "impôt": 16782,
        "surcôte": 7176,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.41
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 100000,
        "impôt": 9200,
        "surcôte": 2180,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.3
    },
    {
        "marié": "oui",
        "enfants": 5,
        "salaire": 100000,
        "impôt": 4230,
        "surcôte": 0,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.14
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 100000,
        "impôt": 22986,
        "surcôte": 0,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.41
    },
    {
        "marié": "oui",
        "enfants": 2,
        "salaire": 30000,
        "impôt": 0,
        "surcôte": 0,
        "décôte": 0,
        "réduction": 0,
        "taux": 0
    },
    {
        "marié": "non",
        "enfants": 0,
        "salaire": 200000,
        "impôt": 64210,
        "surcôte": 7498,
        "décôte": 0,
        "réduction": 0,
        "taux": 0.45
    },
    {
        "marié": "oui",
        "enfants": 3,
        "salaire": 20000,
        "impôt": 0,
        "surcôte": 0,
        "décôte": 0,
        "réduction": 0,
        "taux": 0
    }
]

Deze resultaten komen overeen met die van versie 04.

13.5. Tests [Codeception]

Net zoals in de paragraaf over versie 04 zullen we [Codeception]-tests schrijven voor versie 05.

Image

13.5.1. Test van de laag [dao]

De test [DaoTest.php] is als volgt:


<?php

// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);

// naamruimte
namespace Application;

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

// opname van interfaces en klassen
require_once ROOT . "/Entities/BaseEntity.php";
require_once ROOT . "/Entities/TaxAdminData.php";
require_once ROOT . "/Entities/TaxPayerData.php";
require_once ROOT . "/Entities/Database.php";
require_once ROOT . "/Entities/ExceptionImpots.php";
require_once ROOT . "/Utilities/Utilitaires.php";
require_once ROOT . "/Dao/InterfaceDao.php";
require_once ROOT . "/Dao/TraitDao.php";
require_once ROOT . "/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once ROOT . "/Métier/InterfaceMetier.php";
require_once ROOT . "/Métier/Metier.php";
// bibliotheken van derden
require_once VENDOR . "/autoload.php";

// definitie van constanten
const DATABASE_CONFIG_FILENAME = ROOT ."/Data/database.json";
const TAXADMINDATA_FILENAME = ROOT ."/Data/taxadmindata.json";
const RESULTS_FILENAME = ROOT ."/Data/resultats.json";
const ERRORS_FILENAME = ROOT ."/Data/errors.json";
const TAXPAYERSDATA_FILENAME = ROOT ."/Data/taxpayersdata.json";

class DaoTest extends \Codeception\Test\Unit {
  // TaxAdminData
  private $taxAdminData;

  public function __construct() {
    parent::__construct();
    // aanmaken van de laag [dao]
    $dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

  // tests
  public function testTaxAdminData() {
    // rekenconstanten
    $this->assertEquals(1551, $this->taxAdminData->getPlafondQfDemiPart());

  }

}

Opmerkingen

  • regels 9-33: definitie van de testomgeving. We gebruiken dezelfde omgeving als die van het hoofdscript [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase], beschreven in de paragraaf 'link';
  • regels 39-44: opbouw van de laag [dao];
  • regel 43: het attribuut [$this→taxAdminData] bevat de te testen gegevens;
  • regels 47-51: de methode [testTaxAdminData] is de methode die wordt beschreven in de paragraaf ‘link’;

De testresultaten zijn als volgt:

Image

13.5.2. Test van de laag [métier]

De test [MetierTest.php] is als volgt:


<?php

// strikte naleving van de gedeclareerde typen van functieparameters
declare (strict_types=1);

// naamruimte
namespace Application;

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

// opname van interfaces en klassen
require_once ROOT . "/Entities/BaseEntity.php";
require_once ROOT . "/Entities/TaxAdminData.php";
require_once ROOT . "/Entities/TaxPayerData.php";
require_once ROOT . "/Entities/Database.php";
require_once ROOT . "/Entities/ExceptionImpots.php";
require_once ROOT . "/Utilities/Utilitaires.php";
require_once ROOT . "/Dao/InterfaceDao.php";
require_once ROOT . "/Dao/TraitDao.php";
require_once ROOT . "/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once ROOT . "/Métier/InterfaceMetier.php";
require_once ROOT . "/Métier/Metier.php";
// bibliotheken van derden
require_once VENDOR . "/autoload.php";

// definitie van constanten
const DATABASE_CONFIG_FILENAME = ROOT ."/Data/database.json";
const TAXADMINDATA_FILENAME = ROOT ."/Data/taxadmindata.json";
const RESULTS_FILENAME = ROOT ."/Data/resultats.json";
const ERRORS_FILENAME = ROOT ."/Data/errors.json";
const TAXPAYERSDATA_FILENAME = ROOT ."/Data/taxpayersdata.json";

class MetierTest extends \Codeception\Test\Unit {
  // bedrijfslaag
  private $métier;

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

  // tests
  public function test1() {
    $result = $this->métier->calculerImpot("oui", 2, 55555);
    $this->assertEqualsWithDelta(2815, $result["impôt"], 1);
    $this->assertEqualsWithDelta(0, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.14, $result["taux"]);
  }
…………………………………………………………………………………………………………………..
public function test11() {
    $result = $this->métier->calculerImpot("oui", 3, 200000);
    $this->assertEqualsWithDelta(42842, $result["impôt"], 1);
    $this->assertEqualsWithDelta(17283, $result["surcôte"], 1);
    $this->assertEqualsWithDelta(0, $result["décôte"], 1);
    $this->assertEqualsWithDelta(0, $result["réduction"], 1);
    $this->assertEquals(0.41, $result["taux"]);
  }

}

Opmerkingen

  • regels 9-33: definitie van de testomgeving. We gebruiken dezelfde omgeving als die van het hoofdscript [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase], beschreven in de paragraaf 'link';
  • regels 39-45: opbouw van de lagen [dao] en [métier];
  • regel 44: het attribuut [$this→métier] verwijst naar de laag [métier];
  • regels 47-64: de methoden [test1, test2…, test11] zijn de methoden die worden beschreven in de paragraaf 'link';

De testresultaten zijn als volgt:

Image