13. Praxisübung – Version 5

Wir haben bereits mehrere Versionen dieser Übung geschrieben. Die neueste Version verwendete eine mehrschichtige Architektur:

Die [dao]-Schicht implementiert eine [InterfaceDao]-Schnittstelle. Wir haben eine Klasse erstellt, die diese Schnittstelle implementiert:
- [DaoImportsWithTaxAdminDataInJsonFile], die Steuerdaten aus einer JSON-Datei abrief;
Wir werden die Schnittstelle [InterfaceDao] mit einer neuen Klasse [DaoImportsWithTaxAdminDataInDatabase] implementieren, die Steuerverwaltungsdaten aus einer MySQL-Datenbank abruft.
13.1. Erstellen der Datenbank [dbimpots-2019]
In Anlehnung an das Beispiel im Abschnitt „Link“ erstellen wir eine MySQL-Datenbank mit dem Namen [dbimpots-2019], deren Eigentümer [admimpots] ist und deren Passwort [mdpimpots] lautet:

- In [1-4] oben sehen wir die Datenbank [dbimpots-2019], die derzeit keine Tabellen enthält;

- In [1-5] oben sehen wir, dass der Benutzer [admimpots] über alle Berechtigungen für die Datenbank [dbimpots-2019] verfügt. Was wir hier nicht sehen, ist, dass dieser Benutzer das Passwort [admimpots] hat;
Wir werden nun die Tabelle [tbtranches] erstellen, die die Steuerklassen enthalten wird:

- In [1-7] erstellen wir eine Tabelle namens [tbtranches] mit 4 Spalten;

- In [3-6] definieren wir eine Spalte namens [id] (3) vom Typ Ganzzahl [int] (4), die als Primärschlüssel [6] der Tabelle dient und vom DBMS automatisch inkrementiert [5] wird. Das bedeutet, dass MySQL die Primärschlüsselwerte bei Einfügungen selbst verwaltet. Es weist dem Primärschlüssel des ersten Eintrags den Wert 1 zu, dem nächsten den Wert 2 und so weiter;
- In [7] bietet der Assistent zusätzliche Konfigurationsoptionen für den Primärschlüssel an. Hier übernehmen wir einfach [7] die Standardwerte;

- In [8-16] definieren wir die anderen drei Spalten der Tabelle:
- [limits] (8), eine Dezimalzahl (9) mit 10 Stellen, einschließlich 2 Dezimalstellen (10), enthält die Elemente von Spalte 17 der Steuerklassen;
- [coeffR] (11), eine 6-stellige Dezimalzahl (12) mit 2 Dezimalstellen (13), enthält die Werte für Spalte 18 der Steuerklassen;
- [coeffN] (14) vom Typ Dezimalzahl (15) mit 10 Stellen, einschließlich 2 Dezimalstellen (16), enthält die Elemente der Spalte 19 der Steuerklassen;
Nach der Validierung dieser Struktur erhalten wir folgendes Ergebnis:

- In [5] zeigt das Schlüsselsymbol an, dass die Spalte [id] der Primärschlüssel ist. Wir können auch sehen, dass dieser Primärschlüssel ganzzahlige Werte (6) hat und von MySQL verwaltet (automatisch inkrementiert) wird;
Genauso wie wir die Tabelle [tbtranches] erstellt haben, erstellen wir die Tabelle [tbconstantes], die die bei der Steuerberechnung verwendeten Konstanten enthalten wird:

Es ist möglich, die Datenbankstruktur als eine Folge von SQL-Anweisungen in eine Textdatei zu exportieren:

Die Option [5] exportiert hier nur die Datenbankstruktur, nicht deren Inhalt. In unserem Fall enthält die Datenbank noch keine Daten.



Option [11] generiert die folgende SQL-Datei [dbimpots-2019.sql]:
-- phpMyAdmin SQL Dump
-- version 4.8.5
-- https://www.phpmyadmin.net/
--
-- Host: localhost:3306
-- Generation Time: Jun 30, 2019 at 01:10 PM
-- Server version: 5.7.24
-- PHP Version: 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`;
-- --------------------------------------------------------
--
-- Table structure for table `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;
-- --------------------------------------------------------
--
-- Table structure for table `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;
--
-- Indexes for dumped tables
--
--
-- Indexes for table `tbconstantes`
--
ALTER TABLE `tbconstantes`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `tbtranches`
--
ALTER TABLE `tbtranches`
ADD PRIMARY KEY (`id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `tbconstantes`
--
ALTER TABLE `tbconstantes`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `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 */;
Sie können diese SQL-Datei verwenden, um die Datenbank [dbimpots-2019] neu zu erstellen, falls sie zerstört oder beschädigt wurde. Es ist nicht erforderlich, die Datenbank vor der Neuerstellung zu löschen, da das SQL-Skript dies automatisch übernimmt:


13.2. Code-Organisation
Um die Rolle der verschiedenen PHP-Skripte, die wir schreiben, besser zu veranschaulichen, werden wir unseren Code in Ordner organisieren:

- in [1], Übersicht über Version 05;
- in [2] die Anwendungskomponenten, d. h. die zwischen den Schichten ausgetauschten Komponenten;
- in [3] die Anwendungsdienstprogramme;
- in [4] die von der Anwendung verwendeten oder erzeugten Daten. Hier haben wir uns entschieden, für Textdateien ausschließlich JSON-Dateien zu verwenden. Diese bieten mehrere Vorteile:
- sie werden von vielen Tools erkannt;
- diese Tools unterstützen Syntaxhervorhebung. Darüber hinaus hat JSON strenge Regeln. Wenn diese Regeln nicht befolgt werden, kennzeichnen die Tools sie. Ein häufiger Fehler in einer einfachen Textdatei ist beispielsweise die Verwendung von Groß- oder Kleinbuchstaben „O“ anstelle von Nullen. Tritt dieser Fehler auf, wird er markiert. Im JSON-Code:
"plafondRevenusCouplePourReduction": 42O74
wo versehentlich ein großes O anstelle einer Null in [42074] verwendet wurde, markiert NetBeans den Fehler:

Tatsächlich erkennt NetBeans das große O, wodurch [49O74] zu einer Zeichenkette wird. Es kommt zu dem Schluss, dass die Syntax [4-5] lauten sollte: Die Zeichenkette [47O74] sollte in Anführungszeichen gesetzt werden. Der Entwickler wird somit auf den Fehler aufmerksam gemacht und kann ihn korrigieren: entweder durch Hinzufügen von Anführungszeichen oder durch Ersetzen des O durch eine Null;
Die weiteren Elemente der Version 05 lauten wie folgt:

- in [6] die Schnittstellen und Klassen der [Dao]-Schicht;
- in [7] die Schnittstellen und Klassen der [business]-Schicht;
- in [8] die Hauptskripte der Version 05;
Version 05 hat zwei unterschiedliche Ziele:
- die MySQL-Datenbank [dbimpots-2019] mit dem Inhalt der JSON-Datei [Data/txadmindata.json] zu füllen;
- die Steuerberechnung unter Verwendung von Steuerdaten zu implementieren, die nun aus der MySQL-Datenbank [dbimpots-2019] bezogen werden;
Wir werden diese beiden Ziele getrennt behandeln.
13.3. Befüllen der Datenbank [dbimpots-2019]
13.3.1. Ziel
Die Textdatei taxadmindata.json enthält Daten der Steuerbehörde:
{
"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
}
Unser Ziel ist es, diese Daten in die zuvor erstellte MySQL-Datenbank [dbimpots-2019] zu übertragen.
13.3.2. Die Entitäten

Die Entität [Database] wird verwendet, um die Daten aus der folgenden JSON-Datei [database.json] zu kapseln:
{
"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"
}
Die Entität [TaxAdminData] wird verwendet, um die Daten aus der folgenden JSON-Datei [taxadmindata.json] zu kapseln:
{
"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
}
Die Entität [TaxPayerData] wird verwendet, um die Daten aus der folgenden JSON-Datei [taxpayerdata.json] zu kapseln:
[
{
"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. Die Basisklasse [BaseEntity]
Um den Entity-Code zu vereinfachen, wenden wir folgende Regel an: Die Attribute einer Entity haben dieselben Namen wie die Attribute in der JSON-Datei, die die Entity kapseln soll. Basierend auf dieser Regel weisen die Entities [Database, TaxAdminData, TaxPayerData] gemeinsame Merkmale auf, die in eine übergeordnete Klasse zusammengefasst werden können. Dies ist die folgende Klasse [BaseEntity]:
<?php
namespace Application;
class BaseEntity {
// attribute
protected $arrayOfAttributes;
// initialization from a jSON file
public function setFromJsonFile(string $jsonFilename) {
// retrieve the contents of the tax data file
$fileContents = \file_get_contents($jsonFilename);
$erreur = FALSE;
// mistake?
if (!$fileContents) {
// we note the error
$erreur = TRUE;
$message = "Le fichier des données [$jsonFilename] n'existe pas";
}
if (!$erreur) {
// retrieve the jSON code from the configuration file in an associative array
$this->arrayOfAttributes = \json_decode($fileContents, true);
// mistake?
if ($this->arrayOfAttributes === FALSE) {
// we note the error
$erreur = TRUE;
$message = "Le fichier de données jSON [$jsonFilename] n'a pu être exploité correctement";
}
}
// mistake?
if ($erreur) {
// throw an exception
throw new ExceptionImpots($message);
}
// initialization of class attributes
foreach ($this->arrayOfAttributes as $key => $value) {
$this->$key = $value;
}
// we return the object
return $this;
}
public function checkForAllAttributes() {
// check that all keys have been initialized
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) {
// initialize certain attributes of the
foreach ($arrayOfAttributes as $key => $value) {
$this->$key = $value;
}
// object is returned
return $this;
}
// toString
public function __toString() {
// object attributes
$arrayOfAttributes = \get_object_vars($this);
// remove parent class attribute
unset($arrayOfAttributes["arrayOfAttributes"]);
// object's Json string
return \json_encode($arrayOfAttributes, JSON_UNESCAPED_UNICODE);
}
// getter
public function getArrayOfAttributes() {
return $this->arrayOfAttributes;
}
}
Kommentare
- Zeile 5: Die Klasse [BaseEntity] soll durch die Klassen [Database, TaxAdminData, TaxPayerData] erweitert werden;
- Zeile 7: Das Attribut [$arrayOfAttributes] ist ein Array, das alle Attribute der Unterklasse, die [BaseEntity] erweitert, zusammen mit ihren Werten enthält;
- Zeilen 9–41: Das Attribut [$arrayOfAttributes] wird aus der als Parameter übergebenen JSON-Datei [$jsonFilename] initialisiert. Eine [ExceptionImpot]-Ausnahme wird ausgelöst, wenn die JSON-Datei nicht gelesen werden konnte oder wenn es sich nicht um eine gültige JSON-Datei handelt;
- Zeilen 36–38: Dies ist spezieller Code, der von einer Unterklasse ausgeführt wird. In diesem Fall steht [$this] für eine Instanz der Unterklasse [Database, TaxAdminData, TaxPayerData], und in diesem Szenario initialisieren die Zeilen 36–38 die Attribute dieser Unterklasse, vorausgesetzt, diese Attribute haben die Sichtbarkeit „protected“ (oder „public“) (siehe verlinkten Abschnitt). Wir haben festgestellt, dass die Attribute der Entitäten [Database, TaxAdminData, TaxPayerData] mit den Attributen der JSON-Datei übereinstimmen, die sie kapseln. Schließlich ermöglicht die Methode [setFromJsonFile] einer Unterklasse, sich selbst aus einer JSON-Datei zu initialisieren;
- Zeile 40: Das Objekt [$this] wird auf eine Instanz der Unterklasse gesetzt, wenn die Methode [setFromJsonFile] von einer Unterklasse aufgerufen wurde;
- Zeilen 43–51: Die Methode [checkForAllAttributes] ermöglicht es einer Unterklasse, zu überprüfen, ob alle ihre Attribute initialisiert wurden. Ist dies nicht der Fall, wird eine [ExceptionImpots]-Ausnahme ausgelöst. Diese Methode ermöglicht es der Unterklasse, zu überprüfen, ob in ihrer JSON-Datei bestimmte Attribute nicht ausgelassen wurden;
- Zeilen 53–60: Die Methode [setFromArrayOfAttributes] ermöglicht es einer Unterklasse, alle oder einige ihrer Attribute aus einem assoziativen Array zu initialisieren, dessen Schlüssel dieselben Namen wie die Attribute der zu initialisierenden Unterklasse haben;
- Zeilen 63–70: Die Methode [__toString] liefert die JSON-Darstellung einer Unterklasse;
13.3.2.2. Die Entität [Database]
Die Entität [Database] sieht wie folgt aus:
<?php
namespace Application;
class Database extends BaseEntity {
// attributes
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;
…
}
Die Klasse [Database] dient dazu, die Daten aus der folgenden JSON-Datei [database.json] zu kapseln:
{
"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"
}
Die Klasse und die JSON-Datei haben dieselben Attribute. Diese beschreiben die Eigenschaften der MySQL-Datenbank [dbimpots-2019]:
dsn | Name des Datenbank-DSN |
id | Datenbankbesitzer |
pwd | Ihr Passwort |
tableTranches | Name der Tabelle, die die Steuerklassen enthält |
colLimits colRate colCoeffN | Spaltennamen in der Tabelle [tableTranches] |
tableConstants | Name der Tabelle, die die Konstanten für die Steuerberechnung enthält |
colIncomeCeilingForHalfShare colIncomeLimitSingleForReduction colIncomeLimitCoupleForReduction colHalbanteils-Ermäßigungswert colSteuerfreibetragAlleinstehende colObergrenzePaarabzug colEinkommensobergrenzeEinzelpersonFürAbzug colSteuerobergrenzePaarFürAbzug colMaximalerZehnProzent-Abzug colMinTenPercentDeduction | Spaltennamen in der Tabelle [tableConstants], die Konstanten für die Steuerberechnung enthält |
Warum sollten wir die Tabellen und Spalten benennen, wenn wir ihre Namen bereits kennen und sie sich wahrscheinlich nicht ändern werden? Nach dem MySQL-DBMS werden wir das PostgreSQL-DBMS verwenden, um Daten der Steuerverwaltung zu speichern. Die Spalten- und Tabellennamen in Postgres folgen jedoch nicht denselben Regeln wie in MySQL. Wir werden gezwungen sein, andere Namen zu verwenden. Dies gilt auch für andere DBMS. Wenn wir Code wollen, der zwischen verschiedenen DBMS portierbar ist, ist es vorzuziehen, Parameter anstelle von fest codierten Tabellen- und Spaltennamen zu verwenden.
Kehren wir zum Code für die Klasse [Database] zurück:
<?php
namespace Application;
class Database extends BaseEntity {
// attributes
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
// initialization
public function setFromJsonFile(string $jsonFilename) {
// parent
parent::setFromJsonFile($jsonFilename);
// check that all attributes have been initialized
parent::checkForAllAttributes();
// object is returned
return $this;
}
// getters and setters
public function getDsn() {
return $this->dsn;
}
…
public function setDsn($dsn) {
$this->dsn = $dsn;
return $this;
}
…
}
Kommentare
- Zeilen 7–24: Alle Klassenattribute haben die Sichtbarkeit [protected]. Dies ist eine Voraussetzung dafür, dass sie von der übergeordneten Klasse [BaseEntity] aus geändert werden können (siehe verlinkten Abschnitt);
- Zeilen 28–35: Die Methode [setFromJsonFile] ermöglicht es, die Attribute der Klasse [Database] anhand des Inhalts einer als Parameter übergebenen JSON-Datei zu initialisieren. Die Attribute in der JSON-Datei und diejenigen in der Klasse [Database] müssen identisch sein. Ist die JSON-Datei unbrauchbar, wird eine Ausnahme ausgelöst;
- Zeile 30: Die übergeordnete Klasse führt die Initialisierung durch;
- Zeile 32: Die übergeordnete Klasse wird aufgefordert, zu überprüfen, ob alle Attribute der Klasse [Database] initialisiert wurden. Ist dies nicht der Fall, wird eine Ausnahme ausgelöst;
- Zeile 34: Die soeben initialisierte [Database]-Instanz wird zurückgegeben;
- Zeilen 37 ff.: die Getter und Setter für die Attribute der Klasse;
13.3.2.3. Die Entität [TaxAdminData]
Die Entität [TaxAdminData] sieht wie folgt aus:
<?php
namespace Application;
class TaxAdminData extends BaseEntity {
// tax brackets
protected $limites;
protected $coeffR;
protected $coeffN;
// tax calculation constants
protected $plafondQfDemiPart;
protected $plafondRevenusCelibatairePourReduction;
protected $plafondRevenusCouplePourReduction;
protected $valeurReducDemiPart;
protected $plafondDecoteCelibataire;
protected $plafondDecoteCouple;
protected $plafondImpotCouplePourDecote;
protected $plafondImpotCelibatairePourDecote;
protected $abattementDixPourcentMax;
protected $abattementDixPourcentMin;
…
}
Die Klasse [TaxAdminData] dient dazu, die Daten aus der folgenden JSON-Datei [taxadmindata.json] zu kapseln:
{
"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
}
Die Klasse und die JSON-Datei haben dieselben Attribute. Diese stellen die Daten der Steuerverwaltung dar. Der restliche Code für die Klasse [TaxAdminData] lautet wie folgt:
<?php
namespace Application;
class TaxAdminData extends BaseEntity {
// tax brackets
protected $limites;
protected $coeffR;
protected $coeffN;
// tax calculation constants
protected $plafondQfDemiPart;
protected $plafondRevenusCelibatairePourReduction;
protected $plafondRevenusCouplePourReduction;
protected $valeurReducDemiPart;
protected $plafondDecoteCelibataire;
protected $plafondDecoteCouple;
protected $plafondImpotCouplePourDecote;
protected $plafondImpotCelibatairePourDecote;
protected $abattementDixPourcentMax;
protected $abattementDixPourcentMin;
// initialization
public function setFromJsonFile(string $taxAdminDataFilename) {
// parent
parent::setFromJsonFile($taxAdminDataFilename);
// check that all attributes have been initialized
parent::checkForAllAttributes();
// check that attribute values are real >=0
foreach ($this as $key => $value) {
if ($key !== "arrayOfAttributes") {
// $value must be a real number >=0 or an array of reals >=0
$result = $this->check($value);
// mistake?
if ($result->erreur) {
// throw an exception
throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
} else {
// we note the value
$this->$key = $result->value;
}
}
}
// we return the object
return $this;
}
protected function check($value): \stdClass {
// $value is an array of string elements or a single element
if (!\is_array($value)) {
$tableau = [$value];
} else {
$tableau = $value;
}
// transform the array of strings into an array of reals
$newTableau = [];
$result = new \stdClass();
// table elements must be positive or zero decimal numbers
$modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
for ($i = 0; $i < count($tableau); $i ++) {
if (preg_match($modèle, $tableau[$i])) {
// put the float in newTableau
$newTableau[] = (float) $tableau[$i];
} else {
// we note the error
$result->erreur = TRUE;
// we leave
return $result;
}
}
// we return the result
$result->erreur = FALSE;
if (!\is_array($value)) {
// a single value
$result->value = $newTableau[0];
} else {
// a list of values
$result->value = $newTableau;
}
return $result;
}
// getters and setters
…
}
Kommentare
- Zeile 23: Die Methode [setFromJsonFile] wird verwendet, um die Attribute der Klasse [TaxAdminData] aus einer als Parameter übergebenen JSON-Datei zu initialisieren. Die Attribute in der JSON-Datei müssen dieselben Namen haben wie die in der Klasse;
- Zeile 25: Die übergeordnete Klasse führt diese Aufgabe aus;
- Zeile 27: Die übergeordnete Klasse wird aufgefordert, zu überprüfen, ob alle Attribute der untergeordneten Klasse initialisiert wurden;
- Zeilen 29–42: Wir überprüfen lokal, ob alle Attribute einen positiven reellen Wert haben oder null sind. Diese Überprüfung wurde bereits im Abschnitt „link“ der Version 03 behandelt;
13.3.3. Die [dao]-Schicht
Nun können wir den Code schreiben, der Daten aus der Textdatei [taxadmindata.json] in die Tabellen [tbtranches, tbconstantes] der MySQL-Datenbank [dbimpots-2019] überträgt. Wir werden die folgende Architektur verwenden:


Die [dao]-Schicht wird die folgende Schnittstelle [InterfaceDao4TransferAdminDataFromFile2Database] implementieren:
<?php
// namespace
namespace Application;
interface InterfaceDao4TransferAdminData2Database {
public function transferAdminData2Database(): void;
}
Kommentare
- Zeile 8: Die Methode [transferAdminData2Database] ist für die Speicherung von Steuerverwaltungsdaten in einer Datenbank zuständig;
Die Schnittstelle [InterfaceDao4TransferAdminData2Database] wird von der folgenden Klasse [DaoTransferAdminDataFromJsonFile2Database] implementiert:
<?php
// namespace
namespace Application;
// definition of a TransferAdminDataFromFile2DatabaseDao class
class DaoTransferAdminDataFromJsonFile2Database implements InterfaceDao4TransferAdminData2Database {
// target database attributes
private $database;
// tax administration data
private $taxAdminData;
// manufacturer
public function __construct(string $databaseFilename, string $taxAdminDataFilename) {
// save configuration
$this->database = (new Database())->setFromJsonFile($databaseFilename);
// tax data is stored
$this->taxAdminData = (new TaxAdminData())->setFromJsonFile($taxAdminDataFilename);
}
// transfers tax bracket data from a text file
// to database
public function transferAdminData2Database(): void {
// we work on the basis
$database = $this->database;
try {
// open the database connection
$connexion = new \PDO($database->getDsn(), $database->getId(), $database->getPwd());
// we want every SGBD error to trigger an exception
$connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
// start a transaction
$connexion->beginTransaction();
// fill in the tax bracket table
$this->fillTableTranches($connexion);
// fill in the constants table
$this->fillTableConstantes($connexion);
// the transaction is completed successfully
$connexion->commit();
} catch (\PDOException $ex) {
// is there a transaction in progress?
if (isset($connexion) && $connexion->inTransaction()) {
// transaction ends in failure
$connexion->rollBack();
}
// trace the exception back to the calling code
throw new ExceptionImpots($ex->getMessage());
} finally {
// close the connection
$connexion = NULL;
}
}
// filling the tax bracket table
private function fillTableTranches($connexion): void {
…
}
// filling the constants table
private function fillTableConstantes($connexion): void {
…
}
}
Kommentare
Hier wenden wir das an, was wir im Kapitel über MySQL gelernt haben.
- Zeile 7: Die Klasse [DaoTransferAdminDataFromJsonFile2Database] implementiert die Schnittstelle [InterfaceDao4TransferAdminData2Database];
- Zeile 9: Das Attribut [$database] ist ein Objekt vom Typ [Database], das die Daten aus der Datei [database.json] kapselt;
- Zeile 11: Das Attribut [$taxAdminData] ist ein Objekt vom Typ [TaxAdminData], das die Daten aus der Datei [taxadmindata.json] kapselt;
- Zeilen 14–19: Der Konstruktor erhält die Namen der Dateien [database.json, taxadmindata.json] als Parameter;
- Zeile 16: Initialisierung des Attributs [$database];
- Zeile 18: Initialisierung des Attributs [$taxAdminData];
- Zeile 23: Die einzige Methode der Schnittstelle [InterfaceDao4TransferAdminData2Database] wird implementiert;
- Zeilen 26–38: Die Tabellen [tbtranches] und [tbconstantes] werden in zwei Schritten gefüllt:
- Zeile 34: Zunächst wird die Tabelle [tbtranches] gefüllt. Dies geschieht innerhalb einer Transaktion (Zeilen 32, 38). Die Methode [fillTableTranches] (Zeile 55) löst eine Ausnahme aus, sobald ein Fehler auftritt. In diesem Fall wird die Ausführung mit dem catch/finally-Block in den Zeilen 39–50 fortgesetzt;
- Zeile 36: Die Tabelle [tbconstantes] wird auf die gleiche Weise mithilfe der Methode [fillTableConstantes] (Zeile 60) gefüllt;
- Zeilen 39–47: Fall, in dem vom Code eine Ausnahme ausgelöst wurde;
- Zeilen 41–44: Wenn eine Transaktion existiert, wird sie zurückgesetzt;
- Zeile 46: Es wird eine Ausnahme vom Typ [ExceptionImpots] mit der Meldung der ursprünglichen Ausnahme ausgelöst, die von beliebigem Typ sein kann;
- Zeilen 47–50: In der [finally]-Klausel wird die Verbindung geschlossen;
Der Code für die Methode [fillTableTranches] lautet wie folgt:
private function fillTableTranches($connexion): void {
// raccourci pour la bd
$database = $this->database;
// les données à insérer dans la base de données
$limites = $this->taxAdminData->getLimites();
$coeffR = $this->taxAdminData->getCoeffR();
$coeffN = $this->taxAdminData->getCoeffN();
// on vide la table au cas où il y aurait qq chose dedans
$statement = $connexion->prepare("delete from " . $database->getTableTranches());
$statement->execute();
// on prépare les insertions
$sqlInsert = "insert into {$database->getTableTranches()} "
. "({$database->getColLimites()}, {$database->getColCoeffR()},"
. " {$database->getColCoeffN()}) values (:limites, :coeffR, :coeffN)";
$statement = $connexion->prepare($sqlInsert);
// on exécute l'ordre préparé avec les valeurs des tranches d'impôts
for ($i = 0; $i < count($limites); $i++) {
$statement->execute([
"limites" => $limites[$i],
"coeffR" => $coeffR[$i],
"coeffN" => $coeffN[$i]]);
}
}
Kommentare
- Zeile 1: Die Methode [fillTableTranches] nimmt eine offene Verbindung als Parameter entgegen. Wir wissen außerdem, dass innerhalb dieser Verbindung eine Transaktion gestartet wurde;
- Zeilen 5–7: Die in die Tabelle einzufügenden Werte werden durch das Attribut [$taxAdminData] bereitgestellt;
- Zeilen 9–10: Der aktuelle Inhalt der Tabelle [tbtranches] wird gelöscht;
- Zeilen 12–15: Wir bereiten das Einfügen von Zeilen in die Tabelle vor. Hier verwenden wir die Spaltennamen, die vom Attribut [$database] bereitgestellt werden;
- Zeilen 17–22: Die in den Zeilen 12–15 vorbereitete Einfügeanweisung wird so oft wie nötig ausgeführt;
Der Code für die Methode [fillTableConstantes] lautet wie folgt:
private function fillTableConstantes($connexion): void {
// raccourci
$database = $this->database;
// on vide la table au cas où il y aurait qq chose dedans
$statement = $connexion->prepare("delete from {$database->getTableConstantes()}");
$statement->execute();
// on prépare l'insertion
$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);
// on exécute l'ordre préparé
$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()
]);
}
Kommentare
- Zeile 1: Die Methode [fillTableConstantes] erhält eine offene Verbindung als Parameter. Wir wissen außerdem, dass innerhalb dieser Verbindung eine Transaktion gestartet wurde;
- Zeilen 5–6: Die Tabelle [tbconstantes] wird gelöscht;
- Zeilen 9–31: Vorbereitung der SQL-INSERT-Anweisung. Dies ist komplex, da bei diesem Einfügevorgang 10 Spalten initialisiert werden müssen und die Spaltennamen aus dem Attribut [$database] abgerufen werden müssen;
- Zeilen 33–44: Ausführung der INSERT-Anweisung. Es ist nur eine Zeile einzufügen. Auch hier wird der Code dadurch komplex, dass die einzufügenden Werte aus dem Attribut [$taxAdminData] abgerufen werden müssen;
13.3.4. Das Hauptskript


Das Hauptskript stützt sich bei der Datenübertragung auf die [dao]-Schicht:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// error handling by PHP
// ini_set("display_errors", "0");
// interface and class inclusion
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";
//
// definition of constants
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
//
try {
// creation of the [dao] layer
$dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
// data transfer to the database
$dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
// error is displayed
print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// end
print "Terminé\n";
exit;
Kommentare
- Zeilen 12–21: Laden der Klassen und Schnittstellen der Anwendung;
- Zeilen 24–24: die beiden JSON-Dateien;
- Zeile 30: Instanziierung der [DAO]-Schicht durch Übergabe der beiden JSON-Dateien an den Konstruktor;
- Zeile 32: Die Datenübertragung wird durchgeführt;
Wenn wir diesen Code ausführen, erhalten wir das folgende Ergebnis in der Datenbank:

Spalte [3] zeigt die Werte, die MySQL dem Primärschlüssel [id] zugewiesen hat. Die Nummerierung beginnt bei 1. Der obige Screenshot wurde nach mehrmaliger Ausführung des Skripts aufgenommen.


13.4. Steuerberechnung

13.4.1. Architektur
Version 04 der Steuerberechnungsanwendung verwendete eine mehrschichtige Architektur:

Die [dao]-Schicht implementiert eine Schnittstelle [InterfaceDao]. Wir haben eine Klasse erstellt, die diese Schnittstelle implementiert:
- [DaoImpotsWithTaxAdminDataInJsonFile], die Steuerdaten aus einer JSON-Datei abrief. Das war Version 04;
Wir werden die Schnittstelle [InterfaceDao] mithilfe einer neuen Klasse [DaoImpotsWithTaxAdminDataInDatabase] implementieren, die Steuerverwaltungsdaten aus einer MySQL-Datenbank abruft. Die [dao]-Schicht wird wie zuvor Ergebnisse und Fehler in Textdateien schreiben und ebenfalls Steuerzahlerdaten aus einer Textdatei unter abrufen. Nur dieses Mal handelt es sich bei diesen Textdateien um JSON-Dateien. Außerdem wissen wir, dass die [business]-Schicht nicht geändert werden muss, wenn wir uns weiterhin an die [InterfaceDao]-Schnittstelle halten.

13.4.2. Die Entität [TaxPayerData]

Die Klasse [TaxPayerData] dient dazu, die Daten aus der folgenden JSON-Datei [taxpayersdata.json] in einer Klasse zu kapseln:
[
{
"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
}
]
Die Klasse [TaxPayerData] sieht wie folgt aus:
<?php
// namespace
namespace Application;
// data class
class TaxPayerData extends BaseEntity {
// data required to calculate the taxpayer's tax liability
protected $marié;
protected $enfants;
protected $salaire;
// tax calculation results
protected $impôt;
protected $surcôte;
protected $décôte;
protected $réduction;
protected $taux;
// getters and setters
…
}
Kommentare
- Zeile 7: Die Klasse [TaxPayerData] erweitert die Klasse [BaseEntity]. Da die Methoden der übergeordneten Klasse ausreichen, definiert die Klasse [TaxPayerData] keine eigenen. Beachten Sie, dass die Attribute der Klasse [TaxPayerData] mit denen in der JSON-Datei [taxpayersdata.json] identisch sind;
13.4.3. Die [dao]-Schicht
13.4.3.1. Das Trait [TraitDao]
Das Trait [TraitDao] implementiert einen Teil der Schnittstelle [InterfaceDao]. Sehen wir uns das einmal an:
<?php
// namespace
namespace Application;
interface InterfaceDao {
// reading taxpayer data
public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;
// reading tax data (tax brackets)
public function getTaxAdminData(): TaxAdminData;
// recording results
public function saveResults(string $resultsFilename, array $taxPayersData): void;
}
Das Trait [TraitDao] implementiert die Methoden [getTaxPayersData] und [saveResults] der Schnittstelle [InterfaceDao]. Da sich die Definition der Entität [TaxPayerData] zwischen den Versionen 04 und 05 geändert hat, müssen wir den Code in [TraitDao] aktualisieren:
<?php
// namespace
namespace Application;
trait TraitDao {
// reading taxpayer data
public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array {
// retrieve taxpayer data in a table
$baseEntity = new BaseEntity();
$baseEntity->setFromJsonFile($taxPayersFilename);
$arrayOfAttributes = $baseEntity->getArrayOfAttributes();
// taxpayer data table
$taxPayersData = [];
// error table
$errors = [];
// we loop over the array of attributes of elements of type [TaxPayerData]
$i = 0;
foreach ($arrayOfAttributes as $attributesOfTaxPayerData) {
// check
$error = $this->check($attributesOfTaxPayerData);
if (!$error) {
// one more taxpayer
$taxPayersData[] = (new TaxPayerData())->setFrOmArrayOfAttributes($attributesOfTaxPayerData);
} else {
// an error of + - the invalid data number is noted
$error = ["numéro" => $i] + $error;
$errors[] = $error;
}
// following
$i++;
}
// save errors in a json file
$string = "";
foreach ($errors as $error) {
$string .= \json_encode($error, JSON_UNESCAPED_UNICODE) . "\n";
}
$this->saveString($errorsFilename, $string);
// function result
return $taxPayersData;
}
private function check(array $attributesOfTaxPayerData): array {
// check the data in [$taxPayerData]
// the list of erroneous attributes
$attributes = [];
// marital status must be yes or no
$marié = trim(strtolower($attributesOfTaxPayerData["marié"]));
$erreur = ($marié !== "oui" and $marié !== "non");
if ($erreur) {
// we note the error
$attributes[] = ["marié" => $marié];
}
// the number of children must be a positive integer or zero
$enfants = trim($attributesOfTaxPayerData["enfants"]);
if (!preg_match("/^\d+$/", $enfants)) {
// we note the error
$erreur = TRUE;
$attributes[] = ["enfants" => $enfants];
} else {
$enfants = (int) $enfants;
}
// the salary must be a positive integer or zero (without euro cents)
$salaire = trim($attributesOfTaxPayerData["salaire"]);
if (!preg_match("/^\d+$/", $salaire)) {
// we note the error
$erreur = TRUE;
$attributes[] = ["salaire" => $salaire];
} else {
$salaire = (int) $salaire;
}
// mistake?
if ($erreur) {
// return with error
return ["erreurs" => $attributes];
} else {
// error-free return
return [];
}
}
// recording results
public function saveResults(string $resultsFilename, array $taxPayersData): void {
// save table [$taxPayersData] in text file [$resultsFileName]
// if text file [$resultsFileName] does not exist, it is created
// construction of the jSON results chain
$string = "[" . implode(",
", $taxPayersData) . "]";
// recording this channel
$this->saveString($resultsFilename, $string);
}
// saving table results in a text file
private function saveString(string $fileName, string $data): void {
// save string [$data] in text file [$fileName]
// if text file [$fileName] does not exist, it is created
if (file_put_contents($fileName, $data) === FALSE) {
throw new ExceptionImpots("Erreur lors de l'enregistrement de données dans le fichier texte [$fileName]");
}
}
}
Kommentare
- [TraitDao] implementiert die Methoden [getTaxPayersData] (Zeile 9) und [saveResults] (Zeile 86) der Schnittstelle [InterfaceDao];
- Zeile 9: Die Methode [getTaxPayersData] nimmt die folgenden Parameter entgegen:
- [$taxPayersFilename]: der Name der JSON-Datei, die Steuerzahlerdaten enthält [taxpayersdata.json];
- [$errorsFilename]: der Name der JSON-Datei, die Fehler enthält [errors.json];
- Zeilen 11–13: Der Inhalt der JSON-Datei mit den Steuerzahlerdaten wird in ein assoziatives Array [$arrayOfAttributes] übertragen. Wenn die JSON-Datei unbrauchbar ist, wird eine [ExceptionImpots]-Ausnahme ausgelöst;
- Zeile 15: Das Array [$taxPayersData] enthält Steuerzahlerdaten, die in Objekten vom Typ [TaxPayerData] gekapselt sind;
- Zeile 17: Fehler werden im Array [$errors] gesammelt;
- Zeilen 99–33: Aufbau des Arrays [$taxPayersData];
- Zeile 22: Bevor die Daten in einen [TaxPayerData]-Typ gekapselt werden, werden sie überprüft. Die Methode [check] gibt Folgendes zurück:
- ein Array [‘errors’=>[…]], das die fehlerhaften Attribute enthält, wenn die Daten falsch sind;
- ein leeres Array, wenn die Daten korrekt sind;
- Zeile 25: Wenn die Daten gültig sind, wird ein neues [TaxPayerData]-Objekt erstellt und dem Array [$taxPayersData] hinzugefügt;
- Zeilen 26–30: Fall, in dem die Daten ungültig sind. Der Fehlerdatensatz enthält die ID des fehlerhaften [TaxPayerData]-Objekts in der JSON-Datei, damit der Benutzer es finden kann; anschließend wird der Fehler dem Array [$errors] hinzugefügt;
- Zeilen 35–39: Die aufgetretenen Fehler werden in der JSON-Datei [$errorsFilename] protokolliert, die in Zeile 9 als Parameter übergeben wurde;
- Zeile 41: Das Array der erstellten [TaxPayerData]-Objekte wird zurückgegeben: Dies war das Ziel der Methode;
- Zeilen 44–83: Die private Methode [check] überprüft die Gültigkeit der Parameter [married, children, salary] des Arrays [$attributesOfTaxPayerData], das in Zeile 44 als Parameter übergeben wurde. Wenn ungültige Attribute vorhanden sind, werden diese im Array [$attributes] (Zeilen 47, 53, 60, 70) in der Form eines Arrays [‘invalid attribute’=> Wert des ungültigen Attributs] gesammelt;
- Zeile 78: Wenn Fehler vorliegen, wird ein Array [‘errors’=>$attributes] zurückgegeben;
- Zeile 81: Wenn keine Fehler vorliegen, wird ein leeres Array zurückgegeben;
- Zeilen 86–93: Implementierung der Methode [saveResults] der Schnittstelle [InterfaceDao];
- Zeile 90: Wir erstellen die JSON-Zeichenkette, die in der JSON-Datei [$resultsFilename] gespeichert werden soll, die in Zeile 86 als Parameter übergeben wurde. Wir müssen die JSON-Zeichenkette aus einem Array erstellen:
- Jedes Element des Arrays ist vom nächsten durch ein Komma und einen Zeilenumbruch getrennt;
- das gesamte Array steht in eckigen Klammern [];
- Zeile 92: Die JSON-Zeichenkette wird in der JSON-Datei [$resultsFilename] gespeichert;
13.4.3.2. Die Klasse [DaoImpotsWithTaxAdminDataInDatabase]
Die Klasse [DaoImpotsWithTaxAdminDataInDatabase] implementiert die Schnittstelle [InterfaceDao] wie folgt:
<?php
// namespace
namespace Application;
// definition of a ImpotsWithDataInDatabase class
class DaoImpotsWithTaxAdminDataInDatabase implements InterfaceDao {
// use of a line
use TraitDao;
// the TaxAdminData object containing tax bracket data
private $taxAdminData;
// the [Database] type object containing the characteristics of the BD
private $database;
// manufacturer
public function __construct(string $databaseFilename) {
// store the jSON configuration of the bd
$this->database = (new Database())->setFromJsonFile($databaseFilename);
// we prepare the attribute
$this->taxAdminData = new TaxAdminData();
try {
// open the database connection
$connexion = new \PDO(
$this->database->getDsn(),
$this->database->getId(),
$this->database->getPwd());
// we want every SGBD error to trigger an exception
$connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
// start a transaction
$connexion->beginTransaction();
// fill in the tax bracket table
$this->getTranches($connexion);
// fill in the constants table
$this->getConstantes($connexion);
// the transaction is completed successfully
$connexion->commit();
} catch (\PDOException $ex) {
// is there a transaction in progress?
if (isset($connexion) && $connexion->inTransaction()) {
// transaction ends in failure
$connexion->rollBack();
}
// trace the exception back to the calling code
throw new ExceptionImpots($ex->getMessage());
} finally {
// close the connection
$connexion = NULL;
}
}
// reading data from the database
private function getTranches($connexion): void {
…
}
// reading the constants table
private function getConstantes($connexion): void {
…
}
// returns data for tax calculation
public function getTaxAdminData(): TaxAdminData {
return $this->taxAdminData;
}
}
Kommentare
- Zeile 4: Wir behalten den Namespace bei, der bereits für die anderen Implementierungen der [dao]-Schicht verwendet wird;
- Zeile 7: Die Klasse [DaoImportsWithTaxAdminDataInDatabase] implementiert die Schnittstelle [InterfaceDao];
- Zeile 9: Wir importieren das Trait [TraitDao]. Wir wissen, dass dieses Trait einen Teil der Schnittstelle implementiert. Die einzige noch zu implementierende Methode ist die Methode [getTaxAdminData] in den Zeilen 62–64. Diese Methode gibt einfach das private Attribut [taxAdminData] aus Zeile 11 zurück. Wir können daraus schließen, dass der Konstruktor dieses Attribut initialisieren muss. Das ist seine einzige Aufgabe;
- Zeile 16: Der Konstruktor erhält einen einzigen Parameter, [$databaseFilename], der den Namen der JSON-Datei [database.json] angibt, welche die MySQL-Datenbank [dbimpots-2019] definiert;
- Zeile 18: Die JSON-Datei [$databaseFilename] wird verwendet, um ein [Database]-Objekt zu erstellen, das konstruiert und im Attribut [$database] aus Zeile 13 gespeichert wird. Wenn die JSON-Datei nicht korrekt verarbeitet werden konnte, wird eine [ExceptionImpots]-Ausnahme ausgelöst;
- Zeile 20: Das Objekt [$this→taxAdminData] wird erstellt, das vom Konstruktor initialisiert werden muss;
- Zeilen 22–26: Die Datenbankverbindung wird geöffnet. Beachten Sie die Notation [\PDO] als Verweis auf die PHP-Klasse [PDO]. Da wir uns im Namespace [Application] befinden, würde bei der einfachen Schreibweise [PDO] diesem relativen Namen der aktuelle Namespace vorangestellt, was zur Klasse [Application\PDO] führen würde, die nicht existiert;
- Zeile 28: Tritt ein Fehler auf, löst das DBMS eine \PDOException aus (Zeile 37);
- Zeile 30: Wir starten eine Transaktion. Dies ist eigentlich nicht notwendig, da nur zwei SQL-Anweisungen ausgeführt werden und diese Anweisungen die Datenbank nicht verändern. Wir tun dies jedoch, um uns von anderen Datenbankbenutzern abzugrenzen;
- Zeile 32: Die Steuerklassen-Tabelle [tbtranches] wird mithilfe der privaten Methode [getTranches] aus Zeile 52 gelesen;
- Zeile 34: Die Tabelle mit den Berechnungskonstanten [tbconstantes] wird mithilfe der privaten Methode [getConstantes] aus Zeile 57 gelesen;
- Zeile 36: Wenn wir diese Zeile erreichen, bedeutet dies, dass alles gut gelaufen ist. Wir führen daher den Commit der Transaktion durch;
- Zeilen 37–42: Wenn wir diesen Punkt erreichen, bedeutet dies, dass eine Ausnahme aufgetreten ist. Wir führen daher ein Rollback der Transaktion durch, falls eine im Gange war (Zeilen 39–42). Zeile 44: Um konsistente Ausnahmen zu gewährleisten, werfen wir die empfangene Ausnahmemeldung erneut, diesmal als Ausnahme vom Typ [ExceptionImpots];
- Zeilen 45–48: In allen Fällen (unabhängig davon, ob eine Ausnahme aufgetreten ist oder nicht) schließen wir die Verbindung;
Die Methode [getTranches] lautet wie folgt:
private function getTranches($connexion): void {
// raccourcis
$database = $this->database;
$taxAdminData = $this->taxAdminData;
// on prépare la requête SELECT
$statement = $connexion->prepare(
"select {$database->getColLimites()}," .
" {$database->getColCoeffR()}," .
" {$database->getColCoeffN()}" .
" from {$database->getTableTranches()}");
// on exécute l'ordre préparé avec les valeurs des tranches d'impôts
$statement->execute();
// on exploite le résultat
$limites = [];
$coeffR = [];
$coeffN = [];
// remplissage des trois tableaux
while ($tranche = $statement->fetch(\PDO::FETCH_OBJ)) {
$limites[] = (float) $tranche->{$database->getColLimites()};
$coeffR[] = (float) $tranche->{$database->getColCoeffR()};
$coeffN[] = (float) $tranche->{$database->getColCoeffN()};
}
// on mémorise les données dans l'attribut [$taxAdminData] de la classe
$taxAdminData->setFromArrayOfAttributes([
"limites" => $limites,
"coeffR" => $coeffR,
"coeffN" => $coeffN
]);
}
Kommentare
- Zeile 1: Die Methode erhält [$connexion] als Parameter, bei dem es sich um eine offene Verbindung mit einer laufenden Transaktion handelt;
- Zeilen 2–4: Es werden zwei Verknüpfungen erstellt, um zu vermeiden, dass [$this->database] und [$taxAdminData = $this->taxAdminData] im gesamten Code geschrieben werden müssen. Dabei handelt es sich um Kopien von Objektreferenzen, nicht um Kopien der Objekte selbst;
- Zeilen 6–10: Die SELECT-Anweisung wird vorbereitet und dann in Zeile 12 ausgeführt;
- Zeilen 13–22: Das Ergebnis der SELECT-Anweisung wird verarbeitet. Die empfangenen Informationen werden in drei Arrays [limits, coeffR, coeffN] gespeichert;
- Zeilen 24–28: Die drei Arrays werden verwendet, um das Attribut [$this->taxAdminData] der Klasse zu initialisieren;
Die private Methode [getConstantes] lautet wie folgt:
private function getConstantes($connexion): void {
// raccourcis
$database = $this->database;
$taxAdminData = $this->taxAdminData;
// on prépare la requête SELECT
$select = "select {$database->getColPlafondQfDemiPart()}," .
"{$database->getColPlafondRevenusCelibatairePourReduction()}," .
"{$database->getColPlafondRevenusCouplePourReduction()}," . "{$database->getColValeurReducDemiPart()}," .
"{$database->getColPlafondDecoteCelibataire()}," . "{$database->getColPlafondDecoteCouple()}," .
"{$database->getColPlafondImpotCelibatairePourDecote()}," . "{$database->getColPlafondImpotCouplePourDecote()}," .
"{$database->getColAbattementDixPourcentMax()}," . "{$database->getColAbattementDixPourcentMin()}" .
" from {$database->getTableConstantes()}";
$statement = $connexion->prepare($select);
// on exécute l'ordre préparé
$statement->execute();
// on exploite le résultat - 1 seule ligne ici
$row = $statement->fetch(\PDO::FETCH_OBJ);
// on initialise l'attribut [$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()});
}
Kommentare
- Zeile 1: Die Methode erhält [$connection] als Parameter, bei dem es sich um eine offene Verbindung mit einer laufenden Transaktion handelt;
- Zeilen 2–4: Es werden zwei Verknüpfungen erstellt, um zu vermeiden, dass [$this->database] und [$taxAdminData = $this->taxAdminData] im gesamten Code geschrieben werden müssen. Dabei handelt es sich um Kopien von Objektreferenzen, nicht um Kopien der Objekte selbst;
- Zeilen 6–15: Die SELECT-Anweisung wird vorbereitet und dann in Zeile 15 ausgeführt;
- Zeilen 17–29: Das Ergebnis der SELECT-Anweisung wird verarbeitet. Die abgerufenen Informationen werden verwendet, um das Attribut [$this->taxAdminData] der Klasse zu initialisieren;
Hinweis: Beachten Sie, dass die Klasse nicht vom MySQL-DBMS abhängig ist. Es ist der aufrufende Code, der das verwendete DBMS über den Datenbank-DSN angibt.
13.4.4. Die [Business]-Schicht

- Wir haben soeben die [DAO]-Schicht (3) implementiert;
- Da wir uns an die [InterfaceDao]-Schnittstelle gehalten haben, kann die [Business]-Schicht (2) theoretisch unverändert bleiben. Wir haben jedoch nicht nur die [DAO]-Schicht geändert. Wir haben auch die Entitäten geändert, die von allen Schichten gemeinsam genutzt werden;
Die [Business]-Schicht implementiert die folgende [BusinessInterface]-Schnittstelle:
<?php
// namespace
namespace Application;
interface InterfaceMetier {
// calculating a taxpayer's taxes
public function calculerImpot(string $marié, int $enfants, int $salaire): array;
// batch mode tax calculation
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}
- Zeile 12: Die Methode [executeBatchImports] verwendet nun die JSON-Datei [$taxPayersFileName], während es sich in Version 04 um eine einfache Textdatei handelte. ;
In Version 04 sah die Methode [executeBatchImpots] wie folgt aus:
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
// we let the exceptions coming from the [dao] layer flow upwards
// retrieve taxpayer data
$taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
// results table
$results = [];
// we exploit them
foreach ($taxPayersData as $taxPayerData) {
// tax calculation
$result = $this->calculerImpot(
$taxPayerData->getMarié(),
$taxPayerData->getEnfants(),
$taxPayerData->getSalaire());
// complete [$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"]);
// put the result in the results table
$results [] = $taxPayerData;
}
// recording results
$this->dao->saveResults($resultsFileName, $results);
}
- Zeile 15 ist nun falsch. In der neuen Definition der Klasse [TaxPayerData] existiert die Methode [setMontant] nicht mehr;
In Version 05 wird die Methode [executeBatchImpots] wie folgt aussehen:
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
// we let the exceptions coming from the [dao] layer flow upwards
// retrieve taxpayer data
$taxPayersData = $this->dao->getTaxPayersData($taxPayersFileName, $errorsFileName);
// results table
$results = [];
// we exploit them
foreach ($taxPayersData as $taxPayerData) {
// tax calculation
$result = $this->calculerImpot(
$taxPayerData->getMarié(),
$taxPayerData->getEnfants(),
$taxPayerData->getSalaire());
// complete [$taxPayerData]
$taxPayerData->setFromArrayOfAttributes($result);
// put the result in the results table
$results [] = $taxPayerData;
}
// recording results
$this->dao->saveResults($resultsFileName, $results);
}
Kommentare
- Zeile 15: Anstatt die einzelnen Setter der Klasse [TaxPayerData] zu verwenden, nutzen wir deren globalen Setter [setFromArrayOfAttributes];
- Der Rest des Codes muss nicht geändert werden;
13.4.5. Das Hauptskript

- Wir haben gerade die [DAO]- (3) und [Business-Logik]- (2) Schichten implementiert;
- wir müssen noch das Hauptskript (1) schreiben;
Das Hauptskript ähnelt dem der Version 04:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// error handling by PHP
//ini_set("display_errors", "0");
// interface and class inclusion
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";
//
// definition of constants
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 {
// creation of the [dao] layer
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
// creation of the [business] layer
$métier = new Metier($dao);
// tax calculation in batch mode
$métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// error is displayed
print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// end
print "Terminé\n";
exit;
Kommentare
- Zeilen 12–22: Laden aller Dateien aus Version 05;
- Zeilen 25–29: die Namen der verschiedenen JSON-Dateien der Anwendung;
- Zeile 33: Aufbau der [DAO]-Schicht;
- Zeile 35: Aufbau der [business]-Schicht;
- Zeile 37: Aufruf der Methode [executeBatchImports] der [business]-Schicht;
Ergebnisse
Die Anwendung generiert zwei JSON-Dateien:
- [results.json]: die Ergebnisse der verschiedenen Steuerberechnungen;
- [errors.json]: meldet Fehler, die in der JSON-Datei [taxpayersdata.json] gefunden wurden;
Die Datei [errors.json] sieht wie folgt aus:
{
"numéro": 1,
"erreurs": [
{
"marié": "ouix"
},
{
"enfants": "2x"
},
{
"salaire": "55555x"
}
]
}
Das bedeutet, dass in [taxpayersdata.json] der erste Eintrag in der Tabelle „taxpayers“ falsch ist. Die Datei [taxpayersdata.json] sah wie folgt aus:
[
{
"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
}
]
Die Ergebnisdatei [results.json] sieht wie folgt aus:
[
{
"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
}
]
Diese Ergebnisse stimmen mit denen der Version 04 überein.
13.5. [Codeception]-Tests
Wie bereits im Abschnitt zu Version 04 beschrieben, werden wir [Codeception]-Tests für Version 05 schreiben.

13.5.1. Testen der [dao]-Schicht
Der [DaoTest.php]-Test sieht wie folgt aus:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// root directories
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-05");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// interface and class inclusion
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";
// third-party libraries
require_once VENDOR . "/autoload.php";
// definition of constants
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();
// creation of the [dao] layer
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
$this->taxAdminData = $dao->getTaxAdminData();
}
// tests
public function testTaxAdminData() {
// calculation constants
$this->assertEquals(1551, $this->taxAdminData->getPlafondQfDemiPart());
…
}
}
Kommentare
- Zeilen 9–33: Definition der Testumgebung. Wir verwenden dieselbe wie im Hauptskript [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase], das im verlinkten Abschnitt beschrieben wird;
- Zeilen 39–44: Aufbau der [dao]-Schicht;
- Zeile 43: Das Attribut [$this→taxAdminData] enthält die zu testenden Daten;
- Zeilen 47–51: Die Methode [testTaxAdminData] ist die im verlinkten Abschnitt beschriebene;
Die Testergebnisse lauten wie folgt:

13.5.2. Testen der [business]-Schicht
Der Test [MetierTest.php] sieht wie folgt aus:
<?php
// strict adherence to declared types of function parameters
declare (strict_types=1);
// namespace
namespace Application;
// root directories
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-05");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// interface and class inclusion
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";
// third-party libraries
require_once VENDOR . "/autoload.php";
// definition of constants
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 {
// business layer
private $métier;
public function __construct() {
parent::__construct();
// creation of the [dao] layer
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
// creation of the [business] layer
$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"]);
}
}
Kommentare
- Zeilen 9–33: Definition der Testumgebung. Wir verwenden dieselbe wie im Hauptskript [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase], das im verlinkten Abschnitt beschrieben wird;
- Zeilen 39–45: Aufbau der Schichten [dao] und [business];
- Zeile 44: Das Attribut [$this→business] verweist auf die [business]-Schicht;
- Zeilen 47–64: Die Methoden [test1, test2…, test11] sind diejenigen, die im verlinkten Abschnitt beschrieben sind;
Die Testergebnisse lauten wie folgt:
