14. Anwendungsübung – Version 6

Wir haben soeben die folgende Schichtstruktur implementiert:

Das in den Beispielen verwendete DBMS war MySQL. Im Abschnitt „Link“ haben wir festgestellt, dass nichts in der Klasse, die die [dao]-Schicht implementiert, darauf hindeutete, dass ein bestimmtes DBMS verwendet wurde. Wir werden dies nun anhand eines anderen DBMS, nämlich PostgreSQL, überprüfen. Die Schichtenarchitektur sieht dann wie folgt aus:

14.1. Installation des PostgreSQL-DBMS
Distributionen des PostgreSQL-DBMS sind unter [https://www.postgresql.org/download/] (Stand: Mai 2019) verfügbar. Hier zeigen wir die Installation der 64-Bit-Windows-Version:


- Laden Sie unter [1-4] das DBMS-Installationsprogramm herunter;
Führen Sie das heruntergeladene Installationsprogramm aus:

- Geben Sie in [6] ein Installationsverzeichnis an;

- in [8] wird die Option [Stack Builder] für unsere Zwecke hier nicht benötigt;
- in [10] belassen Sie den Standardwert;

- In [12-13] haben wir hier das Passwort [root] eingegeben. Dies ist das Passwort für den DBMS-Administrator, der den Namen [postgres] trägt. PostgreSQL bezeichnet diesen auch als Superuser;
- In [15] belassen Sie den Standardwert: Dies ist der Listening-Port des DBMS;

- In [17] belassen Sie den Standardwert;
- In [19] die Zusammenfassung der Installationskonfiguration;


Unter Windows wird das PostgreSQL-DBMS als Windows-Dienst installiert, der automatisch startet. Meistens ist dies nicht wünschenswert. Wir werden diese Konfiguration ändern. Geben Sie [services] in die Windows-Suchleiste [24-26] ein:

- In [29] sehen Sie, dass der PostgreSQL-DBMS-Dienst auf „Automatisch“ eingestellt ist. Ändern Sie dies, indem Sie die Diensteigenschaften aufrufen [30]:

- Stellen Sie in [31-32] den Starttyp auf „Manuell“ ein;
- Stoppen Sie in [33] den Dienst;
Wenn Sie das DBMS manuell starten möchten, kehren Sie zur Anwendung [Dienste] zurück, klicken Sie mit der rechten Maustaste auf den Dienst [postgresql] (34) und starten Sie ihn (35).
14.2. Aktivieren der PDO-Erweiterung für das PostgreSQL-DBMS
Wir werden die Datei [php.ini] ändern, die PHP konfiguriert (siehe verlinkten Abschnitt):

- Überprüfen Sie in [2], ob die PostgreSQL-PDO-Erweiterung aktiviert ist. Speichern Sie anschließend die Änderung und starten Sie Laragon neu, damit die Änderung wirksam wird. Überprüfen Sie dann die PHP-Konfiguration direkt in Laragon [3-5].
14.3. Verwaltung von PostgreSQL mit dem [pgAdmin]-Tool
Starten Sie den PostgreSQL-DBMS-Windows-Dienst (siehe verlinkten Abschnitt). Starten Sie anschließend, genau wie zuvor das [services]-Tool, das [pgadmin]-Tool, mit dem Sie das PostgreSQL-DBMS verwalten können [1-3]:

Möglicherweise werden Sie an einer Stelle nach dem Superuser-Passwort gefragt. Der Superuser heißt [postgres]. Dieses Passwort haben Sie bei der Installation des DBMS festgelegt. In diesem Dokument haben wir dem Superuser bei der Installation das Passwort [root] zugewiesen.
- In [4] ist [pgAdmin] eine Webanwendung;
- in [5] die Liste der von [pgAdmin] erkannten PostgreSQL-Server, hier 1;
- in [6] der von uns gestartete PostgreSQL-Server;
- in [7] die DBMS-Datenbanken, hier 1;
- in [8] wird die Datenbank [postgresql] vom Superuser [postgres] verwaltet;
Zunächst erstellen wir einen Benutzer [admimpots] mit dem Passwort [mdpimpots]:


- in [17] haben wir [mdpimpots] eingegeben;

- in [21] den SQL-Code, den das [pgAdmin]-Tool an das PostgreSQL-DBMS sendet. Auf diese Weise kann man die proprietäre SQL-Sprache von PostgreSQL erlernen;
- In [22] wurde nach Bestätigung mit dem [Speichern]-Assistenten der Benutzer [admimpots] erstellt;
Nun erstellen wir die Datenbank [dbimpots-2019]:

Klicken Sie mit der rechten Maustaste auf [23] und dann auf [24-25], um eine neue Datenbank zu erstellen. Legen Sie auf der Registerkarte [26] den Datenbanknamen [27] und den Eigentümer [admimpots] [28] fest.

- In [30] den SQL-Code zum Erstellen der Datenbank;
- In [31] wird nach Bestätigung mit dem [Speichern]-Assistenten die Datenbank [dbimpots-2019] erstellt;
Nun erstellen wir die Tabelle [tbtranches] mit den Spalten [id, limites, coeffr, coeffn]. Eine Besonderheit von PostgreSQL ist, dass bei Spaltennamen die Groß-/Kleinschreibung beachtet werden muss, was bei anderen DBMS normalerweise nicht der Fall ist. So funktioniert bei MySQL die SQL-Anweisung [select limites, coeffR, coeffN from tbtranches] auch dann, wenn die tatsächlichen Spalten in der Tabelle [tbtranches] [LIMITES, COEFFR, COEFFN] lauten. Bei PostgreSQL funktioniert diese SQL-Anweisung nicht. Man könnte dann [select LIMITES, COEFFR, COEFFN from tbtranches] schreiben, aber das würde immer noch nicht funktionieren, da PostgreSQL die Abfrage [select limites, coeffr, coeffn from tbtranches] ausführen würde: Standardmäßig wandelt es Spaltennamen in Kleinbuchstaben um. Um dies zu verhindern, müssen Sie schreiben: [select "LIMITES", "COEFFR", "COEFFN" from tbtranches], d. h., Sie müssen die Spaltennamen in Anführungszeichen setzen. Aus diesen Gründen werden wir den Spalten Namen in Kleinbuchstaben geben. Die Namen von Datenbankobjekten können eine Quelle für Inkompatibilitäten zwischen DBMS sein, da bestimmte Namen in einigen DBMS reservierte Wörter sind, in anderen jedoch nicht.
Wir erstellen die Tabelle [tbtranches]:

- Verwenden Sie die Schaltfläche [40], um Spalten zu erstellen;


- Nachdem Sie den Erstellungsassistenten durch Klicken auf [Speichern] abgeschlossen haben, wird die Tabelle [tbtranches] erstellt [52-53];
Wir müssen dem DBMS mitteilen, dass es den Primärschlüssel [id] selbst generieren soll, wenn eine Zeile in die Tabelle eingefügt wird:

- In [56] greifen wir auf die Eigenschaften des Primärschlüssels [id] zu;
- in [59] legen wir fest, dass die Spalte vom Typ [Identity] ist. Dadurch generiert das DBMS die Primärschlüsselwerte;

- in [62], der für diesen Vorgang generierte SQL-Code;
Die Tabelle [tbtranches] ist nun bereit.
Wir wiederholen die gleichen Schritte, um die Tabelle [tbconstantes] zu erstellen. Hier ist das erwartete Ergebnis:



Die Datenbank [dbimpots-2019] ist nun bereit. Wir werden sie mit Daten füllen.
Wie bei MySQL ist es möglich, die Datenbank [dbimpots-2019] in eine SQL-Datei zu exportieren. Diese SQL-Datei können wir dann importieren, um die Datenbank wiederherzustellen, falls sie verloren geht oder beschädigt wird. Hier exportieren wir nur die Datenbankstruktur und nicht die Daten:


Die generierte Datei sieht wie folgt aus:
--
-- PostgreSQL database dump
--
-- Dumped from database version 11.2
-- Dumped by pg_dump version 11.2
-- Started on 2019-07-04 08:20:31
SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET client_min_messages = warning;
SET row_security = off;
SET default_tablespace = '';
SET default_with_oids = false;
--
-- TOC entry 198 (class 1259 OID 16408)
-- Name: tbconstantes; Type: TABLE; Schema: public; Owner: postgres
--
CREATE TABLE public.tbconstantes (
plafond_qf_demi_part double precision NOT NULL,
id integer NOT NULL,
plafond_revenus_celibataire_pour_reduction double precision NOT NULL,
plafond_revenus_couple_pour_reduction double precision NOT NULL,
valeur_reduc_demi_part double precision NOT NULL,
plafond_decote_celibataire double precision NOT NULL,
plafond_decote_couple double precision NOT NULL,
plafond_impot_celibataire_pour_decote double precision NOT NULL,
plafond_impot_couple_pour_decote double precision NOT NULL,
abattement_dix_pourcent_max double precision NOT NULL,
abattement_dix_pourcent_min double precision NOT NULL
);
ALTER TABLE public.tbconstantes OWNER TO postgres;
--
-- TOC entry 199 (class 1259 OID 16411)
-- Name: tbconstantes_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres
--
ALTER TABLE public.tbconstantes ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME public.tbconstantes_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- TOC entry 196 (class 1259 OID 16399)
-- Name: tbtranches; Type: TABLE; Schema: public; Owner: admimpots
--
CREATE TABLE public.tbtranches (
limites double precision NOT NULL,
id integer NOT NULL,
coeffr double precision NOT NULL,
coeffn double precision NOT NULL
);
ALTER TABLE public.tbtranches OWNER TO admimpots;
--
-- TOC entry 197 (class 1259 OID 16404)
-- Name: tbimpots_id_seq; Type: SEQUENCE; Schema: public; Owner: admimpots
--
ALTER TABLE public.tbtranches ALTER COLUMN id ADD GENERATED ALWAYS AS IDENTITY (
SEQUENCE NAME public.tbimpots_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);
--
-- TOC entry 2694 (class 2606 OID 16429)
-- Name: tbconstantes tbconstantes_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
--
ALTER TABLE ONLY public.tbconstantes
ADD CONSTRAINT tbconstantes_pkey PRIMARY KEY (id);
--
-- TOC entry 2692 (class 2606 OID 16403)
-- Name: tbtranches tbimpots_pkey; Type: CONSTRAINT; Schema: public; Owner: admimpots
--
ALTER TABLE ONLY public.tbtranches
ADD CONSTRAINT tbimpots_pkey PRIMARY KEY (id);
--
-- TOC entry 2821 (class 0 OID 0)
-- Dependencies: 198
-- Name: TABLE tbconstantes; Type: ACL; Schema: public; Owner: postgres
--
GRANT ALL ON TABLE public.tbconstantes TO admimpots;
-- Completed on 2019-07-04 08:20:32
--
-- PostgreSQL database dump complete
--
14.4. Befüllen der Tabelle [tbtranches]
Das haben wir bereits im verlinkten Abschnitt mit dem MySQL-DBMS getan. Wir müssen lediglich die Datei [database.json] anpassen, die die Datenbank beschreibt:

Die Datei [database.json] sieht nun wie folgt aus:
{
"dsn": "pgsql:host=localhost;dbname=dbimpots-2019",
"id": "admimpots",
"pwd": "mdpimpots",
"tableTranches": "public.tbtranches",
"colLimites": "limites",
"colCoeffR": "coeffr",
"colCoeffN": "coeffn",
"tableConstantes": "public.tbconstantes",
"colPlafondQfDemiPart": "plafond_qf_demi_part",
"colPlafondRevenusCelibatairePourReduction": "plafond_revenus_celibataire_pour_reduction",
"colPlafondRevenusCouplePourReduction": "plafond_revenus_couple_pour_reduction",
"colValeurReducDemiPart": "valeur_reduc_demi_part",
"colPlafondDecoteCelibataire": "plafond_decote_celibataire",
"colPlafondDecoteCouple": "plafond_decote_couple",
"colPlafondImpotCelibatairePourDecote": "plafond_impot_celibataire_pour_decote",
"colPlafondImpotCouplePourDecote": "plafond_impot_couple_pour_decote",
"colAbattementDixPourcentMax": "abattement_dix_pourcent_max",
"colAbattementDixPourcentMin": "abattement_dix_pourcent_min"
}
- Zeile 2: Der DSN hat sich geändert; [pgsql] gibt an, dass es sich um das Postgres-DBMS handelt;
- Zeilen 5 und 9: Den Tabellennamen wurde der Name des Schemas vorangestellt, zu dem sie gehören [public]. Dies war streng genommen nicht notwendig, da [public] das Standard-Schema ist, wenn im Tabellennamen kein Schema angegeben ist;
- Zeilen 6–8, 10–19: Die Spaltennamen haben sich geändert;
Das Skript [MainTransferAdminDataFromJsonFile2PostgresDatabase.php] zum Befüllen der Datenbank [dbimpots-2019] lautet wie folgt:
<?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__ . "/../../version-05/Entities/BaseEntity.php";
require_once __DIR__ . "/../../version-05/Entities/TaxAdminData.php";
require_once __DIR__ . "/../../version-05/Entities/TaxPayerData.php";
require_once __DIR__ . "/../../version-05/Entities/Database.php";
require_once __DIR__ . "/../../version-05/Entities/ExceptionImpots.php";
require_once __DIR__ . "/../../version-05/Utilities/Utilitaires.php";
require_once __DIR__ . "/../../version-05/Dao/InterfaceDao.php";
require_once __DIR__ . "/../../version-05/Dao/TraitDao.php";
require_once __DIR__ . "/../../version-05/Dao/InterfaceDao4TransferAdminData2Database.php";
require_once __DIR__ . "/../../version-05/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
Nur die Zeilen 12–21, die die für die Ausführung der Anwendung erforderlichen Dateien laden, ändern sich. Sie ändern sich, weil sich der Wert von [__DIR__] ändert: Er verweist nun auf den Ordner [version-07/Main].
Wenn dieses Skript ausgeführt wird, ergibt sich in der Tabelle [tbtranches] folgendes Ergebnis:

- Klicken Sie mit der rechten Maustaste auf [1] und dann auf [2-3];
- In [4] finden wir die Daten zu den Steuerklassen;
Wir wiederholen denselben Vorgang für die Konstantentabelle [tbconstantes]:



Beachten Sie, dass die Laragon-Anwendung zum Ausführen des Skripts nicht aktiv sein muss: Weder der Apache-Server noch das MySQL-DBMS sind erforderlich. Wir benötigen lediglich das PostgreSQL-DBMS, für das wir den Windows-Dienst gestartet haben.
14.5. Steuerberechnung

Die [dao] (3) und [business] (2) Schichten sind bereits geschrieben. Das Hauptskript für das MySQL-DBMS haben wir bereits im verlinkten Abschnitt geschrieben. Wir müssen lediglich das Skript [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase.php] nehmen und es an das PostgreSQL-DBMS anpassen. Es heißt nun [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php]:

Das Skript [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php] lautet wie folgt:
<?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__ . "/../../version-05/Entities/BaseEntity.php";
require_once __DIR__ . "/../../version-05/Entities/TaxAdminData.php";
require_once __DIR__ . "/../../version-05/Entities/TaxPayerData.php";
require_once __DIR__ . "/../../version-05/Entities/Database.php";
require_once __DIR__ . "/../../version-05/Entities/ExceptionImpots.php";
require_once __DIR__ . "/../../version-05/Utilities/Utilitaires.php";
require_once __DIR__ . "/../../version-05/Dao/InterfaceDao.php";
require_once __DIR__ . "/../../version-05/Dao/TraitDao.php";
require_once __DIR__ . "/../../version-05/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once __DIR__ . "/../../version-05/Métier/InterfaceMetier.php";
require_once __DIR__ . "/../../version-05/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
Nur die Zeilen 12–22, die die für die Ausführung der Anwendung erforderlichen Dateien laden, ändern sich. Sie ändern sich, weil sich der Wert von [__DIR__] ändert: Er verweist nun auf den Ordner [version-07/Main].
Ausführungsergebnisse
Die gleichen wie in früheren Versionen.
14.6. [Codeception]-Tests
Wie bei früheren Versionen validieren wir diese Version mit [Codeception]-Tests:

14.6.1. [DAO]-Layer-Test
Der [DaoTest.php]-Test lautet wie folgt:
<?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-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// interface and class inclusion
require_once ROOT . "/../version-05/Entities/BaseEntity.php";
require_once ROOT . "/../version-05/Entities/TaxAdminData.php";
require_once ROOT . "/../version-05/Entities/TaxPayerData.php";
require_once ROOT . "/../version-05/Entities/Database.php";
require_once ROOT . "/../version-05/Entities/ExceptionImpots.php";
require_once ROOT . "/../version-05/Utilities/Utilitaires.php";
require_once ROOT . "/../version-05/Dao/InterfaceDao.php";
require_once ROOT . "/../version-05/Dao/TraitDao.php";
require_once ROOT . "/../version-05/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
// third-party libraries
require_once VENDOR . "/autoload.php";
// definition of constants
const DATABASE_CONFIG_FILENAME = ROOT ."../Data/database.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() {
…
}
}
Kommentare
- Zeilen 9–28: Definition der Testumgebung. Wir verwenden dieselbe Umgebung wie das im verlinkten Abschnitt beschriebene Hauptskript [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase], allerdings ohne die [business]-Schicht;
- Zeilen 34–39: Aufbau der [dao]-Schicht;
- Zeile 38: Das Attribut [$this→taxAdminData] enthält die zu testenden Daten;
- Zeilen 42–44: Die Methode [testTaxAdminData] ist diejenige, die im verlinkten Abschnitt beschrieben wird;
Die Testergebnisse lauten wie folgt:

14.6.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-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// interface and class inclusion
require_once ROOT . "/../version-05/Entities/BaseEntity.php";
require_once ROOT . "/../version-05/Entities/TaxAdminData.php";
require_once ROOT . "/../version-05/Entities/TaxPayerData.php";
require_once ROOT . "/../version-05/Entities/Database.php";
require_once ROOT . "/../version-05/Entities/ExceptionImpots.php";
require_once ROOT . "/../version-05/Utilities/Utilitaires.php";
require_once ROOT . "/../version-05/Dao/InterfaceDao.php";
require_once ROOT . "/../version-05/Dao/TraitDao.php";
require_once ROOT . "/../version-05/Dao/DaoImpotsWithTaxAdminDataInDatabase.php";
require_once ROOT . "/../version-05/Métier/InterfaceMetier.php";
require_once ROOT . "/../version-05/Métier/Metier.php";
// third-party libraries
require_once VENDOR . "/autoload.php";
// definition of constants
const DATABASE_CONFIG_FILENAME = ROOT . "../Data/database.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() {
…
}
--------------------------------------------------------------------
public function test11() {
…
}
}
Kommentare
- Zeilen 9–28: Definition der Testumgebung. Wir verwenden dieselbe wie im Hauptskript [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase], das im verlinkten Abschnitt beschrieben wird;
- Zeilen 34–40: Aufbau der Schichten [dao] und [business];
- Zeile 39: Das Attribut [$this→business] verweist auf die [business]-Schicht
- Zeilen 43–49: Die Methoden [test1, test2…, test11] sind diejenigen, die im verlinkten Abschnitt beschrieben sind;
Die Testergebnisse lauten wie folgt:
