14. Toepassingsopdracht – versie 6

We hebben zojuist de volgende gelaagde structuur geïmplementeerd:

De SGBD die in de voorbeelden werd gebruikt, was MySQL. In de paragraaf over koppelingen hadden we opgemerkt dat niets in de klasse die de laag [dao] implementeert, erop wees dat er een specifieke SGBD werd gebruikt. Dat gaan we nu controleren door een andere SGBD te gebruiken, namelijk de SGBD PostgreSQL. De gelaagde architectuur ziet er dan als volgt uit:

14.1. Installatie van de SGBD en PostgreSQL
De distributies van de SGBD en PostgreSQL zijn beschikbaar via de URL en [https://www.postgresql.org/download/] (mei 2019). We laten de installatie van de versie voor Windows 64-bits zien:


- in [1-4] downloadt u het installatieprogramma van SGBD;
Start het gedownloade installatieprogramma:

- in [6], geef een installatiemap op;

- in [8] is de optie [Stack Builder] niet nodig voor wat we hier willen doen;
- in [10], laat de waarde staan die wordt weergegeven;

- in [12-13] hebben we hier het wachtwoord [root] ingevoerd. Dit wordt het wachtwoord van de beheerder van SGBD, die [postgres] heet. PostgreSQL noemt hem ook wel de supergebruiker;
- laat bij [15] de standaardwaarde staan: dit is de luisterpoort van SGBD;

- in [17], laat de standaardwaarde staan;
- in [19]: het overzicht van de installatieconfiguratie;


In Windows wordt SGBD PostgreSQL geïnstalleerd als een Windows-service die automatisch wordt gestart. Meestal is dit niet wenselijk. We gaan deze configuratie aanpassen. Typ [services] in de zoekbalk van Windows [24-26]:

- in [29]. We zien dat de service SGBD PostgreSQL in de automatische modus staat. We wijzigen dit door de eigenschappen van de service [30] te openen:

- in [31-32], stel het opstarten in op handmatige modus;
- bij [33]: stop de service;
Wanneer u de SGBD handmatig wilt starten, ga dan terug naar de toepassing [services], klik met de rechtermuisknop op de service [postgresql] (34) en start deze (35).
14.2. Activering van de uitbreiding PDO van SGBD PostgreSQL
We gaan het bestand [php.ini] aanpassen dat PHP configureert (zie paragraaf ‘link’):

- in [2]. Controleer of de extensie PDO van PostgreSQL is ingeschakeld. Sla vervolgens de wijziging op en start Laragon opnieuw op om er zeker van te zijn dat de wijziging wordt doorgevoerd. Controleer vervolgens de configuratie van PHP rechtstreeks vanuit Laragon [3-5].
14.3. Beheer PostgreSQL met de tool [pgAdmin]
Start de Windows-service van SGBD PostgreSQL (zie paragraaf ‘link’). Start vervolgens, op dezelfde manier als u de tool [services] hebt gestart, start u het hulpprogramma [pgadmin], waarmee u de SGBD, PostgreSQL en [1-3] kunt beheren:

Het is mogelijk dat u op een bepaald moment wordt gevraagd om het wachtwoord van de supergebruiker. Dit is [postgres]. U hebt dit wachtwoord ingesteld tijdens de installatie van SGBD. In dit document hebben we tijdens de installatie het wachtwoord [root] aan de supergebruiker toegewezen.
- [4], [pgAdmin] is een webapplicatie;
- in [5] is de lijst met PostgreSQL-servers die door [pgAdmin] zijn gedetecteerd, hier 1;
- in [6], de server PostgreSQL die we hebben gestart;
- in [7], de databases van SGBD, hier 1;
- in [8] wordt de database [postgresql] beheerd door de supergebruiker [postgres];
Laten we eerst een gebruiker [admimpots] aanmaken met het wachtwoord [mdpimpots]:


- bij [17] hebben we [mdpimpots] ingevoerd;

- in [21], de code SQL die de tool [pgAdmin] zal genereren naar SGBD PostgreSQL. Dit is een manier om de eigen taal van SQL te leren, die eigendom is van PostgreSQL;
- in [22] is, na validatie van de wizard [Save], de gebruiker [admimpots] aangemaakt;
Nu maken we de database [dbimpots-2019] aan:

Klik met de rechtermuisknop op [23] en vervolgens op [24-25] om een nieuwe database aan te maken. In het tabblad [26] stellen we de naam van de database [27] in en de eigenaar [admimpots] [28].

- in [30], de code SQL voor het aanmaken van de database;
- in [31], na validatie van de wizard [Save], wordt de database [dbimpots-2019] aangemaakt;
Nu gaan we de tabel [tbtranches] aanmaken met de kolommen [id, limites, coeffr, coeffn]. Een bijzonderheid van PostgreSQL is dat de kolomnamen hoofdlettergevoelig zijn (hoofdletters/kleine letters), wat bij de andere SGBD-tabellen doorgaans niet het geval is. Zo zal bij MySQL de opdracht [select limites, coeffR, coeffN from tbtranches] wel werken, ook al zijn de daadwerkelijke kolommen van de tabel [tbtranches] [LIMITES, COEFFR, COEFFN]. Bij PostgreSQL zal de opdracht SQL niet werken. Je zou dan [select LIMITES, COEFFR, COEFFN from tbtranches] kunnen schrijven, maar dat zal nog steeds niet werken, omdat PostgreSQL de opdracht [select limites, coeffr, coeffn from tbtranches] uitvoert: het zet de kolomnamen standaard in kleine letters. Om dit te voorkomen, moet je schrijven: [select "LIMITES", "COEFFR", "COEFFN" from tbtranches], d.w.z. dat je de kolomnamen tussen aanhalingstekens moet zetten. Om deze redenen zullen we de kolommen namen in kleine letters geven. De namen van objecten in een database kunnen een bron van incompatibiliteit vormen tussen SGBD, aangezien sommige namen in bepaalde SGBD gereserveerde woorden zijn en in andere niet.
We maken de tabel [tbtranches] aan:

- gebruik de knop [40] om kolommen aan te maken;


- nadat de aanmaakwizard via [Save] is voltooid, wordt de tabel [tbtranches] aangemaakt [52-53];
We moeten SGBD aangeven dat het zelf de primaire sleutel [id] moet genereren bij het invoegen van een rij in de tabel:

- in [56] openen we de eigenschappen van de primaire sleutel [id];
- bij [59] wordt aangegeven dat de kolom van het type [Identity] is. Dit heeft tot gevolg dat SGBD de waarden van de primaire sleutel genereert;

- in [62], de code SQL die voor deze bewerking is gegenereerd;
De tabel [tbtranches] is nu klaar.
We herhalen dezelfde bewerkingen om de tabel [tbconstantes] aan te maken. We geven het te verkrijgen resultaat op:



De database [dbimpots-2019] is nu klaar. We gaan deze vullen met gegevens.
Net zoals we bij MySQL hebben gedaan, is het mogelijk om de database [dbimpots-2019] te exporteren naar een bestand SQL. Dit bestand SQL kan vervolgens worden geïmporteerd om de database opnieuw aan te maken als deze verloren is gegaan of beschadigd is geraakt. We exporteren hier alleen de structuur van de database en niet de gegevens:


Het gegenereerde bestand is als volgt:
--
-- PostgreSQL databasedump
--
-- Gedumpt vanuit databaseversie 11.2
-- Gedumpt door pg_dump versie 11.2
-- Gestart op 04-07-2019 om 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-vermelding 198 (klasse 1259 OID 16408)
-- Naam: tbconstantes; Type: TABLE; Schema: public; Eigenaar: 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-vermelding 199 (klasse 1259 OID 16411)
-- Naam: tbconstantes_id_seq; Type: SEQUENCE; Schema: public; Eigenaar: 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-vermelding 196 (klasse 1259 OID 16399)
-- Naam: tbtranches; Type: TABLE; Schema: public; Eigenaar: 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-vermelding 197 (klasse 1259 OID 16404)
-- Naam: tbimpots_id_seq; Type: SEQUENCE; Schema: openbaar; Eigenaar: 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-vermelding 2694 (klasse 2606 OID 16429)
-- Naam: tbconstantes tbconstantes_pkey; Type: CONSTRAINT; Schema: public; Eigenaar: postgres
--
ALTER TABLE ONLY public.tbconstantes
ADD CONSTRAINT tbconstantes_pkey PRIMARY KEY (id);
--
-- TOC-vermelding 2692 (klasse 2606 OID 16403)
-- Naam: tbtranches tbimpots_pkey; Type: CONSTRAINT; Schema: public; Eigenaar: admimpots
--
ALTER TABLE ONLY public.tbtranches
ADD CONSTRAINT tbimpots_pkey PRIMARY KEY (id);
--
-- TOC-vermelding 2821 (klasse 0 OID 0)
-- Afhankelijkheden: 198
-- Naam: TABLE tbconstantes; Type: ACL; Schema: public; Eigenaar: postgres
--
GRANT ALL ON TABLE public.tbconstantes TO admimpots;
-- Voltooid op 04-07-2019 om 08:20:32
--
-- Databasedump PostgreSQL voltooid
--
14.4. Het invullen van tabel [tbtranches]
We hebben dit werk al gedaan met de bestanden SGBD en MySQL in de paragraaf 'link'. We hoeven alleen het bestand [database.json] aan te passen, dat de database beschrijft:

Het bestand [database.json] ziet er dan als volgt uit:
{
"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"
}
- regel 2: het bestand DSN is gewijzigd; [pgsql] geeft aan dat het hier gaat om het Postgres-bestand SGBD;
- regels 5 en 9: de namen van de tabellen zijn voorafgegaan door de naam van het schema waartoe ze behoren, [public]. Dit was niet strikt noodzakelijk, aangezien [public] het standaard gebruikte schema is wanneer er geen schema is opgegeven in de naam van de tabel;
- regels 6-8, 10-19: de kolomnamen zijn gewijzigd;
Het script [MainTransferAdminDataFromJsonFile2PostgresDatabase.php] voor het vullen van de database [dbimpots-2019] is als volgt:
<?php
// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);
// naamruimte
namespace Application;
// foutbeheer door PHP
// ini_set("display_errors", "0");
// opname van interface en klassen
require_once __DIR__ . "/../../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";
//
// definitie van constanten
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
//
try {
// aanmaken van de laag [dao]
$dao = new DaoTransferAdminDataFromJsonFile2Database(DATABASE_CONFIG_FILENAME, TAXADMINDATA_FILENAME);
// gegevensoverdracht naar de database
$dao->transferAdminData2Database();
} catch (ExceptionImpots $ex) {
// de fout wordt weergegeven
print "L'erreur suivante s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// einde
print "Terminé\n";
exit;
Opmerkingen
Alleen de regels 12-21, die de bestanden laden die nodig zijn voor de uitvoering van de applicatie, veranderen. Ze veranderen omdat de waarde [__DIR__] verandert: deze verwijst nu naar de map [version-07/Main].
Wanneer dit script wordt uitgevoerd, krijgt men het volgende resultaat in de tabel [tbtranches]:

- klik met de rechtermuisknop op [1] en vervolgens op [2-3];
- in [4] zijn de gegevens van de belastingschijven inderdaad aanwezig;
We herhalen dit voor de constantentabel [tbconstantes]:



Merk op dat de Laragon-applicatie niet actief hoeft te zijn om het script uit te voeren: we hebben noch de Apache-server, noch de SGBD MySQL nodig. Het enige wat nodig is, is de SGBD PostgreSQL waarvan de Windows-service is gestart.
14.5. Berekening van de belasting

De lagen [dao] (3) en [métier] (2) zijn al geschreven. We hebben het hoofdscript voor de SGBD en MySQL al geschreven in de paragraaf met de link. We hoeven alleen maar het script [MainCalculateImpotsWithTaxAdminDataInMySQLDatabase.php] te hergebruiken en het aan te passen aan SGBD en PostgreSQL. Het heet nu [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php]:

Het script [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase.php] is als volgt:
<?php
// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);
// naamruimte
namespace Application;
// foutbeheer door PHP
//ini_set("display_errors", "0");
// opname van interface en klassen
require_once __DIR__ . "/../../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";
//
// definitie van constanten
const DATABASE_CONFIG_FILENAME = "../Data/database.json";
const TAXADMINDATA_FILENAME = "../Data/taxadmindata.json";
const RESULTS_FILENAME = "../Data/resultats.json";
const ERRORS_FILENAME = "../Data/errors.json";
const TAXPAYERSDATA_FILENAME = "../Data/taxpayersdata.json";
try {
// aanmaken van de laag [dao]
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
// aanmaken van de laag [métier]
$métier = new Metier($dao);
// berekening van de belasting in batchmodus
$métier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (ExceptionImpots $ex) {
// de fout wordt weergegeven
print "Une erreur s'est produite : " . utf8_encode($ex->getMessage()) . "\n";
}
// einde
print "Terminé\n";
exit;
Opmerkingen
Alleen de regels 12-22, die de bestanden laden die nodig zijn voor de uitvoering van de applicatie, veranderen. Ze veranderen omdat de waarde [__DIR__] verandert: deze verwijst nu naar de map [version-07/Main].
Uitvoeringsresultaten
Dezelfde als die in de vorige versies.
14.6. Tests [Codeception]
Net als bij de vorige versies valideren we deze versie met [Codeception]-tests:

14.6.1. Test van de laag [dao]
De test [DaoTest.php] is als volgt:
<?php
// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);
// naamruimte
namespace Application;
// hoofdmappen
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// opname van interfaces en klassen
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";
// bibliotheken van derden
require_once VENDOR . "/autoload.php";
// definitie van constanten
const DATABASE_CONFIG_FILENAME = ROOT ."../Data/database.json";
class DaoTest extends \Codeception\Test\Unit {
// TaxAdminData
private $taxAdminData;
public function __construct() {
parent::__construct();
// aanmaken van de laag [dao]
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
$this->taxAdminData = $dao->getTaxAdminData();
}
// tests
public function testTaxAdminData() {
…
}
}
Opmerkingen
- regels 9-28: definitie van de testomgeving. We gebruiken dezelfde omgeving, zonder de laag [métier], als die welke wordt gebruikt door het hoofdscript [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase], beschreven in de paragraaf 'link';
- regels 34-39: opbouw van de laag [dao];
- regel 38: het attribuut [$this→taxAdminData] bevat de te testen gegevens;
- regels 42-44: de methode [testTaxAdminData] is de methode die wordt beschreven in de paragraaf 'link';
De testresultaten zijn als volgt:

14.6.2. Test van de laag [métier]
De test [MetierTest.php] is als volgt:
<?php
// strikte naleving van de gedeclareerde typen van functieparameters
declare (strict_types=1);
// naamruimte
namespace Application;
// hoofdmappen
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-06");
define("VENDOR", "C:/myprograms/laragon-lite/www/vendor");
// opname van interfaces en klassen
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";
// bibliotheken van derden
require_once VENDOR . "/autoload.php";
// definitie van constanten
const DATABASE_CONFIG_FILENAME = ROOT . "../Data/database.json";
class MetierTest extends \Codeception\Test\Unit {
// bedrijfslaag
private $métier;
public function __construct() {
parent::__construct();
// aanmaken van de laag [dao]
$dao = new DaoImpotsWithTaxAdminDataInDatabase(DATABASE_CONFIG_FILENAME);
// aanmaken van de laag [métier]
$this->métier = new Metier($dao);
}
// tests
public function test1() {
…
}
--------------------------------------------------------------------
public function test11() {
…
}
}
Opmerkingen
- regels 9-28: definitie van de testomgeving. We gebruiken dezelfde omgeving als die van het hoofdscript [MainCalculateImpotsWithTaxAdminDataInPostgresDatabase], beschreven in de paragraaf 'link';
- regels 34-40: opbouw van de lagen [dao] en [métier];
- regel 39: het attribuut [$this→métier] verwijst naar de laag [métier]
- regels 43-49: de methoden [test1, test2…, test11] zijn de methoden die worden beschreven in de paragraaf 'link';
De testresultaten zijn als volgt:
