Skip to content

13. Praxisübung – Version 5

Image

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

Image

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:

Image

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

Image

  • 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:

Image

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

Image

  • 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;

Image

  • 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:

Image

  • 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:

Image

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

Image

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

Image

Image

Image

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:

Image

Image

13.2. Code-Organisation

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

Image

  • 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:

Image

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:

Image

  • 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

Image

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:

Image

Image

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

Image

Image

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:

Image

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.

Image

Image

13.4. Steuerberechnung

Image

13.4.1. Architektur

Version 04 der Steuerberechnungsanwendung verwendete eine mehrschichtige Architektur:

Image

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.

Image

13.4.2. Die Entität [TaxPayerData]

Image

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

Image

  • 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

Image

  • 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.

Image

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:

Image

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:

Image