23. Toepassingsopdracht – versie 12
In dit hoofdstuk gaan we een webapplicatie schrijven die voldoet aan de MVC-architectuur (Model-View-Controller). De applicatie kan haar antwoorden in drie formaten weergeven: jSON, XML, HTML. Er is een aanzienlijke toename in complexiteit tussen wat we nu gaan doen en wat eerder is gedaan. We zullen de meeste concepten die we tot nu toe hebben behandeld hergebruiken en we zullen alle stappen die naar de uiteindelijke applicatie leiden gedetailleerd beschrijven.
23.1. Architectuur MVC
We gaan het zogenaamde MVC-architectuurmodel (Model – View – Controller) als volgt implementeren:

De verwerking van een verzoek van een klant verloopt als volgt:
- 1 - verzoek
De aangevraagde URL-verzoeken zullen de volgende vorm hebbenhttp://machine:port/contexte/….?action=uneAction¶m1=v1¶m2=v2&… De [Contrôleur principal] gebruikt een configuratiebestand om het verzoek naar de juiste controller en de juiste actie binnen die controller te ‘routeren’. Hiervoor maakt het gebruik van het veld [action] van de URL. De rest van het URL [param1=v1¶m2=v2&…] bestaat uit optionele parameters die naar de actie worden doorgegeven. De C van MVC is hier de tekenreeks [Contrôleur principal, Contrôleur / Action]. Als geen enkele controller de gevraagde actie kan verwerken, zal de webserver antwoorden dat de gevraagde URL niet is gevonden.
- 2 - verwerking
- De gekozen actie [2a] kan gebruikmaken van de parameters parami die de [Contrôleur principal] aan haar heeft doorgegeven. Deze kunnen uit verschillende bronnen afkomstig zijn:
- het pad [/param1/param2/…] van de URL,
- de parameters [param1=v1¶m2=v2] van de URL,
- van parameters die door de browser samen met zijn verzoek zijn verzonden;
- Bij de verwerking van het verzoek van de gebruiker kan de actie de laag [métier] [2b] nodig hebben. Zodra het verzoek van de klant is verwerkt, kan dit verschillende reacties oproepen. Een klassiek voorbeeld is:
- een foutmelding als het verzoek niet correct kon worden verwerkt;
- anders een bevestigingsreactie;
- de [Contrôleur / Action] stuurt zijn antwoord [2c] terug naar de hoofdcontroller, samen met een statuscode. Deze statuscodes geven op unieke wijze de toestand van de applicatie weer. Het gaat hierbij om succescodes of foutcodes;
- De gekozen actie [2a] kan gebruikmaken van de parameters parami die de [Contrôleur principal] aan haar heeft doorgegeven. Deze kunnen uit verschillende bronnen afkomstig zijn:
- 3 - antwoord
- afhankelijk van of de client een antwoord jSON, XML of HTML heeft aangevraagd, zal [Contrôleur principal] het juiste antwoordtype [3a] instantiëren en deze vragen het antwoord naar de klant te verzenden. De [Contrôleur principal] zal zowel het antwoord als de statuscode doorgeven die zijn geleverd door de [Contrôleur / Action] die is uitgevoerd;
- als het gewenste antwoord van het type jSON of XML is, zal het geselecteerde antwoord het antwoord van [Contrôleur / Action] dat het heeft ontvangen opmaken en naar [3c] verzenden. De client die dit antwoord kan verwerken, kan een consolescript PHP zijn of een JavaScript-script dat in een pagina HTML is ondergebracht;
- als het gewenste antwoord van het type HTML is, zal het geselecteerde antwoord [3b] een van de weergaven HTML [Vuei] selecteren met behulp van de statuscode die het heeft gekregen. Dit is de V van MVC. Aan elke statuscode is één weergave gekoppeld. Deze weergave V geeft het antwoord weer van de [Contrôleur / Action] die is uitgevoerd. Deze weergave verwerkt de gegevens van dit antwoord met behulp van HTML, CSS en JavaScript. Deze gegevens noemen we het model van de weergave. Dit is de M van MVC. De client is in de meeste gevallen een browser;
Laten we nu het verband tussen webarchitectuur MVC en gelaagde architectuur verduidelijken. Afhankelijk van de definitie die men aan het model geeft, zijn deze twee concepten al dan niet met elkaar verbonden. Laten we een webapplicatie MVC met één laag nemen:

Hierboven bevatten de [Contrôleur / Action]-lagen elk een deel van de lagen [métier] en [dao]. In de laag [web] is er weliswaar sprake van een MVC-architectuur, maar de applicatie als geheel heeft geen gelaagde architectuur. Hier is er slechts één laag die alles doet.
Laten we nu eens kijken naar een meerlaagse webarchitectuur:

De laag [web] kan worden geïmplementeerd zonder het model MVC te volgen. We hebben dan wel een meerlaagse architectuur, maar de weblaag implementeert het model MVC niet.
Bijvoorbeeld: in de .NET-omgeving kan de hierboven genoemde laag [web]bovenstaande laag worden geïmplementeerd met ASP.NET en MVC, waardoor we een gelaagde architectuur krijgen met een [web]-laag van het type MVC. Zodra dit is gebeurd, kan deze laag ASP.NET MVC worden vervangen door een klassieke laag ASP.NET (WebForms), terwijl de rest (bedrijfslaag, DAO, driver) ongewijzigd. We hebben dan een gelaagde architectuur met een laag [web] die niet langer van het type MVC is.
In MVC hebben we gezegd dat het model M dat van de weergave V is, c.a.d. De verzameling gegevens die door de weergave V wordt weergegeven. Er wordt een andere definitie van het model M van MVC gegeven:

Veel auteurs zijn van mening dat wat zich rechts van de laag [web] bevindt, het model M van MVC vormt. Om dubbelzinnigheden te voorkomen, kan men spreken van:
- het domeinmodel wanneer men alles bedoelt wat zich rechts van de laag [web] bevindt;
- het weergavemodel wanneer we verwijzen naar de gegevens die door een weergave V worden getoond;
23.2. Boomstructuur van het NetBeans-project
Voor het NetBeans-project zullen we een architectuur hanteren die het model MVC weerspiegelt:

- [3]: [main.php] is de hoofdcontroller van ons model MVC. Dit is de C van MVC;
- [4]: het bestand [Controllers] bevat de secundaire controllers. Elk daarvan verwerkt een specifieke actie. Deze actie wordt aangegeven in het bestand URL, bijvoorbeeld […/main.php?action=authentifier-utilisateur]. Met deze actie zal het bestand [Contrôleur principal] [main.php] een [Contrôleur secondaire] selecteren, in dit geval [AuthentifierUtilisateurController], om de gevraagde actie te verwerken. Deze controllers maken ook deel uit van de C van MVC;
- [5]: de map [Model] bevat de lagen [métier] en [dao] van de applicatie. Volgens de eerder gehanteerde termen vertegenwoordigen deze elementen het domeinmodel en kunnen ze, volgens de voor de M gehanteerde terminologie, de M van MVC vertegenwoordigen;
- [6]: de map [Responses] bevat de klassen die verantwoordelijk zijn voor het verzenden van het antwoord naar de klant. Er is één klasse per gewenst antwoordtype:
- [JsonResponse]: voor een antwoord jSON;
- [XmlResponse]: voor een antwoord XML;
- [HtmlResponse]: voor een antwoord HTML;
- [7]: de map [Views] bevat de weergaven HTML wanneer een antwoord HTML gewenst is. Dit is de V van MVC. Ze worden geactiveerd door de klasse [HtmlResponse], die de weer te geven gegevens naar hen doorstuurt. Deze gegevens vormen het model van de weergave. Volgens de terminologie die voor de M is aangenomen, kunnen deze gegevens de M van MVC zijn;
- [8]: de map [Utilities] bevat hulpprogramma’s:
- [Logger]: de klasse waarmee logs in een tekstbestand kunnen worden bijgehouden;
- [Sendmail]: de klasse waarmee e-mails kunnen worden verzonden;
- [9]: de map [Logs] bevat het logbestand [logs.txt];
- [10]: de map [Entities] bevat klassen die door de verschillende controllers worden gebruikt;
Aan de hand van deze boomstructuur kan het verwerkingstraject van een door een klant aangevraagde actie worden beschreven:
- [main.php] [3] ontvangt het verzoek;
- na enkele voorlopige controles (behoort de actie tot de toegestane acties?) stuurt hij het verzoek door naar de secundaire controller [4], die verantwoordelijk is voor de verwerking van deze actie;
- de secundaire controller voert zijn taken uit. Bij zijn werk kan hij de lagen [métier], [dao] en [5] nodig hebben, evenals de entiteiten uit het dossier [10]. Hij stuurt zijn antwoord terug naar de hoofdcontroller [main.php] die hem heeft geactiveerd;
- afhankelijk van het type antwoord [jSON, XML, HTML] dat de klant wenst, activeert de hoofdcontroller [main.php] een van de antwoorden uit het dossier [Responses] [6];
- de antwoorden [JsonResponse, XmlResponse] sturen respectievelijk het antwoord jSON of XML naar de klant;
- het antwoord [HtmlResponse] gebruikt een van de weergaven uit de map [Views] [7] om een antwoord HTML naar de client te sturen;
- de verschillende controllers hebben toegang tot de klasse [Logger] van de map [8] om logboeken te schrijven in het logboekbestand van de map [9]. Het volgende wordt gelogd:
- de gevraagde actie;
- het antwoord van de betreffende controller. Dit wordt opgeslagen in het formaat jSON, ongeacht het aangevraagde type [jSON, XML, HTML];
- bij een fatale fout (HTTP_INTERNAL_SERVER_ERROR) stuurt de hoofdcontroller [main.php] een e-mail naar de beheerder met behulp van de klasse [SendMail] uit de map [8];
23.3. De acties van de applicatie
De client stuurt de uit te voeren actie naar de webserver in de vorm van een parameter [action] in de URL [/main.php?action=xxx]. De toegestane acties staan vermeld in het bestand [config.json], dat de hoofdcontroller [main.php] configureert:
"actions":
{
"init-session": "\\InitSessionController",
"authentifier-utilisateur": "\\AuthentifierUtilisateurController",
"calculer-impot": "\\CalculerImpotController",
"lister-simulations": "\\ListerSimulationsController",
"supprimer-simulation": "\\SupprimerSimulationController",
"fin-session": "\\FinSessionController",
"afficher-calcul-impot": "\\AfficherCalculImpotController"
},
- regel 1: de sleutel [actions] van het woordenboek jSON;
- regels 3-9: een woordenboek [action:contrôleur]. Aan elke actie is de secundaire controller gekoppeld die deze moet verwerken;
- regel 3: [init-session]: start een sessie met simulaties van belastingberekeningen. Deze actie geeft het gewenste type antwoorden aan: [jSON, XML, HTML];
- regel 4: zodra het type sessie is vastgesteld, moet de klant zich authenticeren met de actie [authentifier-utilisateur]. Zolang hij niet is geïdentificeerd, zijn alle andere acties verboden, met uitzondering van [init-session];
- regel 5: zodra de klant is geïdentificeerd, kan hij een reeks belastingberekeningen uitvoeren met de actie [calculer-impot];
- regel 6: de klant kan op elk moment de lijst met simulaties die hij heeft uitgevoerd, opvragen met de actie [lister-simulations];
- regel 7: hij kan bepaalde simulaties verwijderen met de actie [supprimer-simulation];
- regel 8: de klant beëindigt zijn simulatiesessie met de actie [fin-session]. Vanaf dat moment moet hij zich opnieuw aanmelden als hij de applicatie wil gebruiken;
- regel 9: in de applicatie HTML roept de actie [afficher-calcul-impot] het formulier op waarmee de belasting kan worden berekend;
23.4. Configuratie van de webapplicatie
De applicatie wordt geconfigureerd via het volgende bestand: jSON [config.json]:
{
"databaseFilename": "database.json",
"rootDirectory": "C:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-12",
"relativeDependencies": [
"/Entities/BaseEntity.php",
"/Entities/Simulation.php",
"/Entities/Database.php",
"/Entities/TaxAdminData.php",
"/Entities/ExceptionImpots.php",
"/Utilities/Logger.php",
"/Utilities/SendAdminMail.php",
"/Model/InterfaceServerDao.php",
"/Model/ServerDao.php",
"/Model/ServerDaoWithSession.php",
"/Model/InterfaceServerMetier.php",
"/Model/ServerMetier.php",
"/Responses/InterfaceResponse.php",
"/Responses/ParentResponse.php",
"/Responses/JsonResponse.php",
"/Responses/XmlResponse.php",
"/Responses/HtmlResponse.php",
"/Controllers/InterfaceController.php",
"/Controllers/InitSessionController.php",
"/Controllers/ListerSimulationsController.php",
"/Controllers/AuthentifierUtilisateurController.php",
"/Controllers/CalculerImpotController.php",
"/Controllers/SupprimerSimulationController.php",
"/Controllers/FinSessionController.php",
"/Controllers/AfficherCalculImpotController.php"
],
"absoluteDependencies": [
"C:/myprograms/laragon-lite/www/vendor/autoload.php",
"C:/myprograms/laragon-lite/www/vendor/predis/predis/autoload.php"
],
"users": [
{
"login": "admin",
"passwd": "admin"
}
],
"adminMail": {
"smtp-server": "localhost",
"smtp-port": "25",
"from": "guest@localhost",
"to": "guest@localhost",
"subject": "plantage du serveur de calcul d'impôts",
"tls": "FALSE",
"attachments": []
},
"logsFilename": "Logs/logs.txt",
"actions":
{
"init-session": "\\InitSessionController",
"authentifier-utilisateur": "\\AuthentifierUtilisateurController",
"calculer-impot": "\\CalculerImpotController",
"lister-simulations": "\\ListerSimulationsController",
"supprimer-simulation": "\\SupprimerSimulationController",
"fin-session": "\\FinSessionController",
"afficher-calcul-impot": "\\AfficherCalculImpotController"
},
"types": {
"json": "\\JsonResponse",
"html": "\\HtmlResponse",
"xml": "\\XmlResponse"
},
"vues": {
"vue-authentification.php": [700, 221, 400],
"vue-calcul-impot.php": [200, 300, 341, 350, 800],
"vue-liste-simulations.php": [500, 600]
},
"vue-erreurs": "vue-erreurs.php"
}
Opmerkingen
- regel 2: naam van het bestand jSON dat de configuratie voor de toegang tot de database bevat;
- regels 3-39: configuratie van de projectafhankelijkheden. Hier worden alle PHP-scripts uit de projectstructuur opgesomd;
- regels 40-44: de gebruiker die bevoegd is om de applicatie te gebruiken;
- regels 46-54: de e-mailgegevens van de beheerder van de applicatie;
- regel 55: het pad naar het logbestand;
- regels 56-65: koppelingen [action => contrôleur secondaire chargé de la traiter];
- regels 66-70: koppelingen [type de réponse => classe Response chargée d’envoyer la réponse au client];
- regels 71-75: koppelingen [vue HTML => tableau des codes d’état menant à cette vue];
- regel 76: de weergave [vue-erreurs] wordt weergegeven in een sessie HTML telkens wanneer er een abnormale fout optreedt:
- een applicatie jSON of XML wordt doorgaans opgevraagd met een geprogrammeerde client. Deze stuurt parameters naar de server die ontbreken of onjuist kunnen zijn. Alle controllers verwerken deze gevallen en sturen foutcodes terug naar de client. Alle mogelijke foutgevallen moeten worden verwerkt;
- bij een HTML-applicatie ligt het iets anders. Bij normaal gebruik maakt de webapplicatie slechts gebruik van een deel van de mogelijke gebruiksscenario’s van de jSON- en XML-clients. Laten we een voorbeeld nemen: de actie [calculer-impot] verwacht drie geposte parameters (verzonden door een POST): [marié, enfants, salaire].
- Als we een client jSON hebben waarmee we URL handmatig kunnen invoeren, dan kan men de actie [calculer-impot] aanvragen met een GET in plaats van een POST, of met een POST zonder enige geposte parameter terwijl er drie nodig zijn, enzovoort… De server jSON moet al deze gevallen verwerken;
- Bij een webapplicatie wordt de actie [calculer-impot] aangevraagd via een webformulier, waarbij geen van beide voorgaande gevallen mogelijk is: de actie [calculer-impot] wordt aangevraagd met een POST en de drie parameters [marié, enfants, salaire]. Sommige van deze parameters kunnen een onjuiste waarde hebben, maar ze zullen wel aanwezig zijn. De gebruiker kan echter bepaalde fouten nabootsen door zelf URL in de browser in te voeren. Uit veiligheidsoverwegingen moet dit geval worden afgehandeld;
- de weergave [vue-erreurs] wordt telkens weergegeven wanneer een secundaire controller een statuscode retourneert die niet compatibel is met de webapplicatie, d.w.z. een statuscode die niet voorkomt op de regels 72-74 van het configuratiebestand. We kiezen voor deze oplossing om didactische redenen. Een andere mogelijke optie zou zijn om niets te doen en gewoon de weergave die op dat moment in de browser van de klant te zien is opnieuw weer te geven, zodat de gebruiker de indruk krijgt dat de server niet reageert op zijn handmatig gegenereerde URL;
23.5. Installatie van tools en bibliotheken
23.5.1. Postman
[Postman] is de tool waarmee we de verschillende URL van onze webapplicatie kunnen opvragen. Hiermee kunnen we:
- elk willekeurig URL te gebruiken: deze zijn handmatig gemaakt;
- verzoeken te sturen naar de webserver via een GET, POST, PUT, OPTIONS…;
- de parameters van de GET of de POST te specificeren;
- de headers HTTP van het verzoek vast te stellen;
- een antwoord te ontvangen in het formaat jSON, XML, HTML,
- toegang te krijgen tot de headers HTTP van het antwoord. Zo hebben we dus toegang tot het volledige antwoord HTTP van de server;
Aangezien we de opgevraagde URL-verzoeken handmatig samenstellen, kunnen we alle mogelijke foutgevallen testen en zien hoe de server reageert.
[Postman] is beschikbaar via URL en [https://www.getpostman.com/downloads/]. De versie die in juni 2019 beschikbaar is, is 7.2. Deze versie vertoont een afwijking: wanneer er opeenvolgende verzoeken worden gedaan aan de betreffende webserver, stuurt de client [Postman 7.2] de cookies die de server hem stuurt niet automatisch terug, met name de sessiecookie. Om de sessie in stand te houden, moet het sessiecookie dan handmatig worden overgenomen in de HTTP-headers van de opeenvolgende verzoeken. Dit is niet erg ingewikkeld, maar wel onhandig. Het betreft een bug die in eerdere versies niet voorkwam. Het team van [Postman] was zich bewust van de bug en heeft deze verholpen in een (mogelijk onstabiele) alfa-versie met de naam [Postman Canary], die beschikbaar is via de URL [https://www.getpostman.com/downloads/canary]. Dit is de versie die hier wordt gebruikt. We zullen de installatie ervan beschrijven. Als er een stabiele versie [Postman 7.3] of hoger beschikbaar is, kunt u deze downloaden: de bug is dan waarschijnlijk verholpen.
Ga verder met de installatie van uw versie van [Postman]. Tijdens de installatie wordt u gevraagd een account aan te maken: dit is hier niet nodig. Het account [Postman] dient om verschillende apparaten te synchroniseren, zodat de configuratie van het ene apparaat naar het andere wordt gekopieerd. Dit alles is hier niet nodig.
Na de installatie toont [Postman] de volgende interface:

- in [2-3] heb je toegang tot de instellingen van het product;

- in [6], de versie die in dit document wordt gebruikt;
- als u een account hebt aangemaakt, vindt er een synchronisatie plaats tussen uw computer en een externe server [Postman]. Dit wordt gesymboliseerd door het tandwiel [7] dat draait telkens wanneer u wijzigingen aanbrengt in het project [Postman]. Om deze onnodige synchronisatie te stoppen, meldt u zich af in [8-9];
23.5.2. De Symfony / Serializer-bibliotheek
Om objecten in jSON en XML te serialiseren, gaan we de bibliotheek [Symfony / Serializer] gebruiken. Deze biedt hier twee voordelen:
- deze is consistent in het gebruik voor het serialiseren naar jSON of XML: hierdoor hoeven we niet twee verschillende API (Application Programming Interface) te leren;
- het kan objecten standaard serialiseren naar jSON of XML, zelfs als de attributen daarvan privé zijn. We herinneren ons dat in jSON, om een object te serialiseren, de klasse ervan de interface [\JsonSerializable] moest implementeren. Het resultaat was dan de tekenreeks jSON van een associatieve array met de attributen van de klasse als sleutels. Bij het deserialiseren van deze tekenreeks jSON kreeg men de primitieve associatieve array terug, die vervolgens moest worden omgezet in een object van de klasse die was geserialiseerd. Met [Symfony / Serializer] levert de deserialisatie direct een object van de geserialiseerde klasse op. Dat is eenvoudiger;
De documentatie van de bibliotheek [Symfony / Serializer] is beschikbaar op URL: [https://symfony.com/doc/current/components/serializer.html] (juni 2019).
Om deze bibliotheek te installeren, opent u een Laragon-terminal (zie paragraaf ‘link’) en voert u de volgende opdracht in:

- in [1], het installatiecommando voor de bibliotheek [symfony/serializer];
- in [2], een andere bibliotheek die nodig is voor ons project: maakt het serialiseren van objecten mogelijk;

23.6. De entiteiten van de applicatie

De entiteiten [BaseEntity, Database, ExceptionImpots, TaxAdminData] worden al vanaf versie 08 van de webservice gebruikt (zie paragraaf 'link').
De klasse [Simulation] zal worden gebruikt om de elementen van een belastingberekeningssimulatie in te kapselen:
<?php
namespace Application;
class Simulation extends BaseEntity {
// attributen van een simulatie van de belastingberekening
protected $marié;
protected $enfants;
protected $salaire;
protected $impôt;
protected $surcôte;
protected $décôte;
protected $réduction;
protected $taux;
// getters
public function getMarié() {
return $this->marié;
}
public function getEnfants() {
return $this->enfants;
}
public function getSalaire() {
return $this->salaire;
}
public function getImpôt() {
return $this->impôt;
}
public function getSurcôte() {
return $this->surcôte;
}
public function getDécôte() {
return $this->décôte;
}
public function getRéduction() {
return $this->réduction;
}
public function getTaux() {
return $this->taux;
}
}
Opmerkingen
- regel 5: de klasse [Simulation] is een uitbreiding van de klasse [BaseEntity] en erft dus de volgende methoden:
- [setFromArrayOfAttributes($arrayOfAttributes)]: waarmee de attributen van de klasse kunnen worden geïnitialiseerd;
- [__toString]: waarmee de tekenreeks jSON van het object wordt geretourneerd;
- regels 7-14: de attributen van de simulatie;
- regels 16-47: de getters van de klasse;
23.7. De hulpprogramma's van de applicatie
![]()
Met de klasse [Logger] kunnen gebeurtenissen in een tekstbestand worden gelogd. Deze klasse is beschreven in de paragraaf 'link'.
Met de klasse [SendAdminMail] kan een e-mail naar de beheerder van de applicatie worden verzonden. Deze klasse is beschreven in de paragraaf [link].
23.8. De lagen [métier] en [dao]


De klassen en interfaces van de lagen [métier] en [dao] zijn verzameld in de map [Model]. Ze zijn allemaal gedefinieerd en gebruikt in eerdere versies:
ExceptionImpots | De klasse van uitzonderingen die door de laag [dao] worden gegenereerd. Gedefinieerd in de paragraaf ‘link’. |
InterfaceServerDao | Interface geïmplementeerd door de [dao]-laag van de server. Gedefinieerd in de paragraaf ‘link’. |
ServerDao | Implementatie van de interface [InterfaceServerDao]. Implementeert de laag [dao] van de server. Gedefinieerd in de paragraaf 'link'. |
ServerDaoWithSession | Implementatie van de interface [InterfaceServerDao]. Implementeert de serverlaag [dao]. Gedefinieerd in de paragraaf ‘link’. |
InterfaceServerMetier | Interface geïmplementeerd door de laag [métier] van de server. Gedefinieerd in de paragraaf ‘link’. |
ServerMetier | Implementatie van de interface [InterfaceMetier]. Implementeert de [metier]-laag van de server. Gedefinieerd in de paragraaf 'link'. |
De applicatie die momenteel wordt geschreven, maakt veel gebruik van elementen die al zijn geïntroduceerd en gebruikt:
- de lagen [métier] en [dao];
- de hulpprogramma's [Logger] en [SendAdminMail];
- de entiteiten [ExceptionImpots, TaxAdminData, Database];
We zullen ons concentreren op de laag [web] van de applicatie:

23.9. De hoofdcontroller [main.php]
23.9.1. Inleiding

- [1-2]: de hoofdcontroller [main.php] [1] wordt geconfigureerd door het bestand [config.json] [2];
Laten we nog eens kijken naar de positie van de hoofdcontroller in onze architectuur MVC:

In [1] is de hoofdcontroller [main.php] het eerste element van de architectuur MVC dat het verzoek van de klant verwerkt. Deze vervult verschillende rollen:
- hij voert eerst de basiscontroles uit:
- bestaat het configuratiebestand en is het geldig;
- het laden van alle afhankelijkheden van het project. Dit komt neer op het laden van alle elementen van de architectuur MVC;
- is de gevraagde actie gespecificeerd? Zo ja, is deze geldig?
- als de gevraagde actie geldig is, selecteert hij [2a] de secundaire controller die deze zal verwerken en geeft hij de benodigde informatie door: het verzoek HTTP, de sessie, de configuratie van de applicatie;
- haal het antwoord van de secundaire controller op ([2c]). Afhankelijk van het type (jSON, XML, HTML) van de door de klant gevraagde applicatie, selecteer [3a] het antwoord (JsonResponse, XmlResponse, HtmlResponse) die belast is met het verzenden van het antwoord naar de klant en alle benodigde informatie aan hem doorgeven (het verzoek HTTP, de sessie, de configuratie van de applicatie, het antwoord van de secundaire controller);
- zodra dit antwoord is verzonden ([3c]), de middelen vrij te maken die mogelijk zijn ingezet voor de verwerking van het verzoek;
23.9.2. [main.php] - 1
De code van de hoofdcontroller [main.php] is als volgt:
<?php
// strikte naleving van de gedeclareerde typen van functieparameters
declare (strict_types=1);
// naamruimte
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
// foutbeheer door PHP
//ini_set("display_errors", "0");
error_reporting(E_ALL && !E_WARNING && !E_NOTICE);
// de configuratie wordt opgehaald
$configFilename = "config.json";
$fileContents = \file_get_contents($configFilename);
$erreur = FALSE;
// fout?
if (!$fileContents) {
// de fout wordt genoteerd
$état = 131;
$erreur = TRUE;
$message = "Le fichier de configuration [$configFilename] n'existe pas";
}
if (!$erreur) {
// de code JSON uit het configuratiebestand wordt opgehaald en opgeslagen in een associatieve array
$config = \json_decode($fileContents, true);
// fout?
if (!$config) {
// de fout wordt genoteerd
$erreur = TRUE;
$état = 132;
$message = "Le fichier de configuration [$configFilename] n'a pu être exploité correctement";
}
}
// fout?
if ($erreur) {
// het antwoord JSON van de server wordt voorbereid
// het configuratiebestand kan niet worden gebruikt
// Symfony-afhankelijkheden
require_once "C:/myprograms/laragon-lite/www/vendor/autoload.php";
// antwoord wordt voorbereid
$response = new Response();
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// statuscode
$response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
// inhoud
$response->setContent(json_encode(["action" => "", "état" => $état, "réponse" => $message], JSON_UNESCAPED_UNICODE));
// verzending
$response->send();
// einde
exit;
}
…
Opmerkingen
- regels 10-12: de hoofdcontroller maakt gebruik van de volgende Symfony-objecten:
- [Request]: de HTTP-verzoek dat momenteel wordt verwerkt;
- [Session]: de sessie van de webapplicatie;
- [Response]: het antwoord HTTP aan de klant;
- regel 15: tijdens de gehele ontwikkeling laten we deze regel als commentaar staan: de fouten PHP worden dan opgenomen in de tekststroom die naar de client wordt verzonden. Als deze client een browser is, kunnen zo de fouten worden gezien die de server tegenkomt. Dit helpt bij het opsporen van fouten;
- regel 16: alle fouten worden gemeld (E_ALL), behalve waarschuwingen (! E_WARNING) en niet-kritieke meldingen (! E_NOTICE). Als een bestand bijvoorbeeld niet kan worden geopend, genereert PHP een fout van het type [E_NOTICE]. Als in regel 15 is ingesteld dat fouten moeten worden weergegeven, verschijnt de fout bij het openen van het bestand in de clientbrowser. Dat is prima als u bent vergeten het resultaat van het openen van het bestand te testen, maar minder prettig als u de test wel had gepland: een regel met [notice] vervuilt dan het antwoord van de server aan de client. Tijdens de ontwikkelingsfase zou ook regel 16 moeten worden uitgecommentarieerd: u wilt immers geen enkele fout missen;
- regel 19: het configuratiebestand wordt ingelezen;
- regels 22-27: als het lezen mislukt, wordt de fout geregistreerd (regel 25), wordt de applicatie in de status [131] gezet en wordt een foutmelding opgesteld;
- regel 30: de tekenreeks jSON uit het configuratiebestand wordt gedecodeerd;
- regels 32-37: als het decoderen misgaat, wordt de fout geregistreerd (regel 34), wordt de applicatie in de status [132] gezet en wordt een foutmelding opgesteld;
- regels 40-57: als er een fout optreedt bij het lezen van het configuratiebestand, kunnen we niet verder. We stellen dan een antwoord jSON op voor de klant:
- regel 44: aangezien het configuratiebestand niet is ingelezen, moet het bestand [autoload], dat nodig is voor [Symfony], handmatig worden geïmporteerd;
- regels 46-47: we stellen een antwoord jSON op;
- regel 50: de code HTTP van het antwoord wordt 500 INTERNAL_SERVER_ERROR;
- regel 52: de inhoud van het antwoord wordt vastgesteld op jSON. Alle antwoorden van de onderzochte webapplicatie zullen drie sleutels bevatten:
- [action]: de door de client aangevraagde actie;
- [état]: de status van de applicatie na uitvoering van deze actie;
- [réponse]: het antwoord van de webserver;
- regel 54: het antwoord jSON wordt naar de client verzonden;
23.9.3. Tests [Postman] - 1
We gaan het gedrag van de server controleren wanneer het configuratiebestand ontbreekt of onjuist is:

We gaan de verschillende verzoeken die onze client [Postman] naar de belastingserver zal sturen, in collecties samenvoegen.
- Maak in [1] een nieuwe verzameling aan;
- in [2], geef deze een naam;
- in [3] is de beschrijving optioneel;

- in de collecties [4] verschijnt nu een collectie met de naam [impots-server-tests-version12] [5];
- in [6] kan een nieuwe zoekopdracht aan de collectie worden toegevoegd;

- in [7], geef je de zoekopdracht een naam;
- in [8] is de beschrijving optioneel;

- in [9-11] wordt de zoekopdracht aan de verzameling toegevoegd;
- in [12], keuze van het type verzoek, hier een verzoek van het type [GET]. In [19], de verschillende beschikbare verzoektypes;
- in [13], hier voer je de URL van de server in;
- in [14] voer je hier de parameters in die aan de URL zijn toegevoegd en die dus parameters van de GET zullen zijn. Het voordeel van het hier invoeren in plaats van rechtstreeks in het URL is dat ze door [Postman] URL-gecodeerd worden. Als u ze zelf in het URL invoert, is het aan u om ze URL- te coderen;
- in [15] dient [Authorization] om de gebruiker te definiëren die zich gaat aanmelden. We hoeven deze mogelijkheid niet te gebruiken;
- in [16]: de headers HTTP die bij het verzoek worden meegestuurd. Een aantal headers wordt automatisch in het verzoek opgenomen. U kunt hier nieuwe toevoegen;
- In [17] verwijst [Body] naar de parameters van een bewerking [POST]. We zullen deze optie moeten gebruiken;
We gaan de volgende test uitvoeren:
- in [main.php] geven we aan dat het configuratiebestand [config2.json] is, dat niet bestaat:

- regel 16 van de code moet worden gedecommentarieerd;
- regel 18: de fout met betrekking tot de naam van het configuratiebestand;
Laten we [Postman] [13, 20] openen, het URL van de webserver voor belastingberekening, en het uitvoeren [21]:

Het antwoord dat door de server wordt teruggestuurd (Laragon moet uiteraard actief zijn) is als volgt:

- in [22] heeft de server de code HTTP [500 Internal Server Error] teruggestuurd;
- in [23] verwijst [Body] naar de hoofdtekst van het antwoord, d.w.z. het document dat door de server is verzonden achter de headers HTTP [28];
- in [26] zien we dat [Postman] een antwoord jSON heeft ontvangen;
- in [27] is het antwoord jSON opgemaakt;
- in [28] is het onbewerkte antwoord jSON zonder opmaak;
- in [29] wordt de modus [Preview] gebruikt wanneer het antwoord van het type HTML is. De modus [Preview] geeft dan de ontvangen pagina weer;
- in [30] wordt het antwoord jSON van de server weergegeven. Dit is inderdaad het antwoord dat we verwachtten;
In [25] zijn de headers HTTP die in het antwoord van de server zijn verzonden, als volgt:

- in [32], het type jSON van het antwoord;
Deze eerste test heeft ons laten zien dat:
- elk type verzoek naar de geteste server kan worden verzonden;
- de parameters van GET of POST kunnen instellen;
- het volledige antwoord hebben: de headers HTTP en het document dat op deze headers volgt, [Body];
Laten we nu een tweede test uitvoeren:

- in [1-3]; het bestand [config3.json] is een syntactisch onjuist jSON-bestand;
- in [4] is [main.php] geconfigureerd om [config3.json] te gebruiken;
We voegen een nieuwe aanvraag toe in [Postman]:

- In [1-3] klikken we met de rechtermuisknop op [2] en kiezen we de optie [duplicate] om de query [2] te dupliceren;
- in [4] heeft de nieuwe query een vooraf gedefinieerde naam die we wijzigen in [5];

- in [6], de hernoemde query;
- in [9-10], we sturen dezelfde aanvraag GET als eerder;

- in [11], het antwoord jSON van de server;
We hebben hier laten zien hoe de verschillende acties van de webservice voor belastingberekening getest zouden worden.
23.9.4. [main.php] – 2
We gaan verder met de analyse van de code van de hoofdcontroller [main.php]:
<?php
// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);
// naamruimte
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
// foutbeheer door PHP
//ini_set("display_errors", "0");
error_reporting(E_ALL && !E_WARNING && !E_NOTICE);
// de configuratie wordt opgehaald
$configFilename = "config.json";
…
// de benodigde afhankelijkheden worden in het script opgenomen
$rootDirectory = $config["rootDirectory"];
foreach ($config["relativeDependencies"] as $dependency) {
require_once "$rootDirectory$dependency";
}
// absolute afhankelijkheden (bibliotheken van derden)
foreach ($config["absoluteDependencies"] as $dependency) {
require_once "$dependency";
}
// het logbestand wordt aangemaakt
try {
$logger = new Logger($config['logsFilename']);
} catch (ExceptionImpots $ex) {
// het logbestand kon niet worden aangemaakt - interne serverfout
$état = 133;
(new JsonResponse())->send(
NULL, NULL, $config,
Response::HTTP_INTERNAL_SERVER_ERROR,
["action" => "non déterminée", "état" => $état, "réponse" => "Le fichier de logs [{$config['logsFilename']}] n'a pu être créé"],
[]);
// voltooid
exit;
}
Opmerkingen
- regel 18: er is nu een configuratiebestand [config.json] dat bestaat en syntactisch correct is. Daarnaast zou moeten worden getest of de verwachte sleutels in dit bestand daadwerkelijk aanwezig zijn. We gaan ervan uit dat dit deel uitmaakt van het normale debugwerk van de ontwikkelaar. We hadden dezelfde redenering kunnen volgen voor de twee voorgaande fouten;
- regels 20-28: we nemen alle benodigde afhankelijkheden voor het webproject op. We zijn deze code al meerdere keren tegengekomen;
- regels 31-43: we proberen het object [Logger] aan te maken, waarmee we gebeurtenissen kunnen loggen in het bestand [$config['logsFilename']]. Het aanmaken hiervan kan mislukken;
- regels 33-43: afhandeling van de fout bij het aanmaken van het object [Logger];
- regel 35: we stellen een statusnummer in;
- regels 36-40: er wordt een antwoord jSON verzonden;
- regel 42: het script wordt beëindigd;
Alle naar de client verzonden antwoorden implementeren de volgende interface [InterfaceResponse]:

De code van de interface [InterfaceResponse] is als volgt:
<?php
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
interface InterfaceResponse {
// Verzoek $request: verzoek wordt verwerkt
// Sessie $session: de sessie van de webapplicatie
// array $config: de configuratie van de applicatie
// int statusCode: de statuscode van het antwoord
// array $content: het antwoord van de server
// array $headers: de headers HTTP die aan het antwoord moeten worden toegevoegd
// Logger $logger: de logger om logbestanden te schrijven
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void;
}
- regels 19-27: de interface [InterfaceResponse] heeft één enkele methode, [send], om het antwoord naar de client te verzenden;
- regels 11-17: de betekenis van de verschillende parameters van de methode [send];
- regels 23-25: de parameters [$statusCode, $content, $headers] bevinden zich in de standaarduitvoer van de secundaire controllers van de applicatie. Het antwoord kan echter aanvullende informatie nodig hebben. Daarom worden de eerste drie parameters (regels 20-22) aan het antwoord meegegeven, waardoor het toegang krijgt tot alle informatie over het verzoek, de sessie en de configuratie;
- regel 26: het antwoord heeft de parameter [Logger] nodig omdat het het naar de klant verzonden antwoord gaat loggen;
De klasse [JsonResponse] implementeert de interface [InterfaceResponse] als volgt:
<?php
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
class JsonResponse extends ParentResponse implements InterfaceResponse {
// Verzoek $request: verzoek wordt verwerkt
// Sessie $session: de sessie van de webapplicatie
// array $config: de configuratie van de applicatie
// int statusCode: de statuscode van het antwoord
// array $content: het antwoord van de server
// array $headers: de headers HTTP die aan het antwoord moeten worden toegevoegd
// Logger $logger: de logger om logbestanden te schrijven
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void {
// voorbereiding van de Symfony-serializer
$serializer = new Serializer(
[
// nodig voor het serialiseren van objecten
new ObjectNormalizer()],
// encoder jSON
// voor de opties, voeg OU toe tussen de verschillende opties
[new JsonEncoder(new JsonEncode([JsonEncode::OPTIONS => JSON_UNESCAPED_UNICODE]))]
);
// serialisatie jSON
$json = $serializer->serialize($content, 'json');
// headers
$headers = array_merge($headers, ["content-type" => "application/json"]);
// antwoord verzenden
parent::sendResponse($statusCode, $json, $headers);
// logboek
if ($logger !== NULL) {
$logger->write("réponse=$json\n");
}
}
}
Opmerkingen
- regel 13: de klasse implementeert de interface [InterfaceResponse];
- regel 13: de klasse is een afgeleide van de klasse [ParentResponse]. Alle typen van [Response] zijn afgeleid van deze klasse. Het is deze bovenliggende klasse die het antwoord naar de client stuurt (regel 46). Omdat deze code gemeenschappelijk was voor alle typen van [Response], is deze gefactoreerd in een bovenliggende klasse;
- regels 33-40: instantiëren van de serializer [Symfony] die het antwoord van de server [$content] omzet in een string jSON (regel 42);
- regels 34-36: de eerste parameter van de constructor van [Serializer] is een array. Hierin wordt een instantie van de klasse [ObjectNormalizer] geplaatst, die nodig is voor de serialisatie van objecten. Dit komt in deze applicatie voor bij een lijst met simulaties, waarbij elke simulatie een instantie is van de klasse [Simulation];
- regel 39: de tweede parameter van de constructor van [Serializer] is eveneens een array: hierin worden alle encoders geplaatst die bij een serialisatie worden gebruikt (XML, jSON, CSV…);
- regel 39: hier zal slechts één encoder staan, van het type [JsonEncoder]. De constructor zonder parameters zou voldoende zijn geweest. Hier hebben we een parameter [JsonEncode] doorgegeven aan de constructor, uitsluitend om de coderingsopties jSON door te geven;
- regel 39: de parameter van de constructor [JsonEncode] is een array met opties. Hier gebruiken we de optie [JSON_UNESCAPED_UNICODE] om aan te geven dat de tekens UTF-8 in de tekenreeks jSON in hun oorspronkelijke vorm moeten worden weergegeven en niet moeten worden ‘geëscape’;
- regel 42: de hoofdtekst van het antwoord HTTP wordt geserialiseerd tot jSON met behulp van de voorgaande serializer;
- regel 44: we voegen de header HTTP toe, die de client laat weten dat we hem jSON gaan sturen;
- regel 46: we vragen de bovenliggende klasse om het antwoord naar de klant te sturen;
- regels 48-50: we loggen het antwoord jSON;
De code van de bovenliggende klasse [ParentResponse] is als volgt:
<?php
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Response;
class ParentResponse {
// int $statusCode: de statuscode HTTP van het antwoord
// string $content: de inhoud van het te verzenden antwoord
// afhankelijk van het geval is dit een tekenreeks jSON, XML, HTML
// array $headers: de headers HTTP die aan het antwoord moeten worden toegevoegd
public function sendResponse(
int $statusCode,
string $content,
array $headers): void {
// voorbereiding van het tekstantwoord van de server
$response = new Response();
$response->setCharset("utf-8");
// statuscode
$response->setStatusCode($statusCode);
// headers
foreach ($headers as $text => $value) {
$response->headers->set($text, $value);
}
// het antwoord wordt verzonden
$response->setContent($content);
$response->send();
}
}
Opmerkingen
- regels 10-13: de betekenis van de drie parameters van de methode [send];
- regel 17: merk op dat de inhoud van het antwoord van het type [string] is en dus klaar is om te worden verzonden (regel 30);
- regel 22: het antwoord zal UTF-8-tekens bevatten;
- regel 24: statuscode HTTP van het antwoord;
- regels 26-28: toevoeging van de headers HTTP die door de aanroepende code zijn opgegeven;
- regels 30-31: verzending van het antwoord naar de klant;
We hebben de volledige cyclus van een jSON-antwoord gedetailleerd beschreven. We zullen hier verderop niet meer op terugkomen. Het volstaat om de handtekening van de interface [InterfaceResponse] te onthouden:
interface InterfaceResponse {
// Verzoek $request: verzoek wordt verwerkt
// Sessie $session: de sessie van de webapplicatie
// array $config: de configuratie van de applicatie
// int statusCode: de statuscode van het antwoord
// array $content: het antwoord van de server
// array $headers: de headers HTTP die aan het antwoord moeten worden toegevoegd
// Logger $logger: de logger om logbestanden te schrijven
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void;
}
De hoofdcontroller [main.php] moet deze handtekening telkens respecteren wanneer hij vraagt om het antwoord naar de klant te verzenden.
23.9.5. Tests [Postman] – 2
We wijzigen het bestand [config.json] als volgt:

- in [1] geven we aan dat het logbestand [Logs] is, wat een map [2] is. Het aanmaken van het bestand [Logs] zou dus moeten mislukken;
We maken een nieuwe aanvraag [Postman] [3], genaamd [erreur-133]:

- [2-4]: we definiëren dezelfde aanvraag als in de twee voorgaande tests;
- [5-7]: we ontvangen inderdaad het verwachte antwoord jSON;
23.9.6. [main.php] – 3
Laten we verdergaan met het onderzoek van de hoofdcontroller [main.php]:
<?php
// strikte naleving van de gedeclareerde typen van functieparameters
declare (strict_types=1);
// naamruimte
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
// foutbeheer door PHP
…
// aanmaken van het logbestand
…
// eerste log
$logger->write("\n---nouvelle requête\n");
// huidige verzoek
$request = Request::createFromGlobals();
// sessie
$session = new Session();
$session->start();
// foutenlijst
$erreurs = [];
$erreur = FALSE;
// de gevraagde actie wordt verwerkt
if (!$request->query->has("action")) {
$erreurs[] = "paramètre [action] manquant";
$erreur = TRUE;
$état = 101;
$action = "";
} else {
// de actie wordt opgeslagen
$action = strtolower($request->query->get("action"));
}
// de actie wordt gelogd
$logger->write("action [$action] demandée\n");
// Bestaat de actie?
if (!$erreur && !array_key_exists($action, $config["actions"])) {
$erreurs[] = "action [$action] invalide";
$erreur = TRUE;
$état = 102;
}
// het sessietype moet bekend zijn voordat bepaalde acties kunnen worden uitgevoerd
if (!$erreur && !$session->has("type") && $action !== "init-session") {
$erreurs[] = "pas de session en cours. Commencer par action [init-session]";
$erreur = TRUE;
$état = 103;
}
// voor bepaalde acties moet men geauthenticeerd zijn
if (!$erreur && !$session->has("user") && $action !== "authentifier-utilisateur" && $action !== "init-session") {
$erreurs[] = "action demandée par utilisateur non authentifié";
$erreur = TRUE;
$état = 104;
}
// fouten?
if ($erreurs) {
// het antwoord wordt voorbereid zonder het te verzenden
$statusCode = Response::HTTP_BAD_REQUEST;
$content = ["réponse" => $erreurs];
$headers = [];
} else {
// ---------------------------
// de actie wordt uitgevoerd met behulp van de bijbehorende controller
$controller = __NAMESPACE__ . $config["actions"][$action];
$logger->write("contrôleur : $controller\n");
list($statusCode, $état, $content, $headers) = (new $controller())->execute($config, $request, $session);
}
// --------------------- het antwoord wordt verzonden
// in geval van een fatale fout HTTP_INTERNAL_SERVER_ERROR
// er wordt een e-mail naar de beheerder gestuurd indien mogelijk
if ($statusCode === Response::HTTP_INTERNAL_SERVER_ERROR && $config['adminMail'] != NULL) {
$infosMail = $config['adminMail'];
$infosMail['message'] = json_encode($content, JSON_UNESCAPED_UNICODE);
$sendAdminMail = new SendAdminMail($infosMail, $logger);
$sendAdminMail->send();
}
// het antwoord hangt af van het type sessie
if ($session->has("type")) {
// het sessietype staat in de sessie
$type = $session->get("type");
} else {
// als er geen type in de sessie staat, dan is het standaardantwoord in jSON
$type = "json";
}
// de sleutels [action, état] worden aan het antwoord van de controller toegevoegd
$content = ["action" => $action, "état" => $état] + $content;
// we instantiëren het object [Response] dat verantwoordelijk is voor het verzenden van het antwoord naar de client
$response = __NAMESPACE__ . $config["types"][$type]["response"];
(new $response())->send($request, $session, $config, $statusCode, $content, $headers, $logger);
// het antwoord is verzonden – de bronnen worden vrijgegeven
$logger->close();
exit;
Opmerkingen
- zodra de eerste controles zijn uitgevoerd en hij weet dat hij aan de slag kan, richt de hoofdcontroller zich op de actie die van hem is gevraagd: deze moet aan bepaalde voorwaarden voldoen;
- regel 21: we loggen het feit dat er een nieuw verzoek is. Dit konden we eerder niet doen omdat we niet zeker wisten of we een geldig logbestand hadden;
- regel 23: alle informatie uit het verzoek van de klant wordt ingekapseld in het Symfony-object [Request];
- regel 26: we starten een nieuwe sessie of halen de bestaande sessie op, indien deze bestaat;
- regel 27: de sessie wordt geactiveerd;
- regel 29: een array met foutmeldingen;
- regel 30: een booleaanse waarde die ons tijdens het testen aangeeft of er al dan niet een fout is opgetreden;
- regel 32: de parameter [action] moet deel uitmaken van URL in de vorm [main.php?action=uneAction]. De parameter [action] maakt dan deel uit van de parameters [$request→query];
- regels 33-36: het geval waarin de parameter [action] ontbreekt in de URL. De fout wordt genoteerd en er wordt een status [101] aan toegekend;
- regel 39: als de parameter [action] aanwezig is in het URL, wordt deze opgeslagen;
- regel 42: het type actie wordt geregistreerd;
- regels 45-49: als de parameter [action] aanwezig is, moet deze geldig zijn. Alle toegestane acties zijn gedefinieerd in de associatieve tabel [$config["actions"]];
- regels 46-48: als de actie ongeldig is, wordt de fout geregistreerd en krijgt deze de status [102] toegewezen;
- regels 52-56: er is sprake van een geldige actie. Deze moet nog aan andere voorwaarden voldoen. De webapplicatie levert drie soorten antwoorden (jSON, XML, HTML). Dit type wordt bepaald door de actie [init-session]. Deze actie plaatst het sessietype in de sleutel [type];
- regel 52: buiten de actie [init-session] om moet elke andere actie plaatsvinden met een sleutel [type] in de sessie;
- regels 53-55: als dit niet het geval is, wordt de fout geregistreerd en krijgt de sessie de status [103] toegewezen;
- regels 58-63: met uitzondering van de acties [init-session] en [authentifier-utilisateur] moeten alle andere acties na authenticatie worden uitgevoerd. Deze authenticatie vindt plaats met behulp van de actie [authentifier-utilisateur], die bij succesvolle authenticatie een sleutel [user] in de sessie plaatst;
- regel 59: als de actie noch [init-session] noch [authentifier-utilisateur] is en de sleutel [user] niet in de sessie aanwezig is, dan is er sprake van een fout;
- regels 60-62: de fout wordt geregistreerd en krijgt de status [104] toegewezen;
- regels 66-71: er wordt gecontroleerd of de array [$erreurs] niet leeg is. Als dat het geval is, is de gevraagde actie of de uitvoeringscontext ervan onjuist;
- regels 68-70: het antwoord dat naar de klant moet worden verzonden, wordt voorbereid, maar nog niet verzonden;
- regel 68: statuscode HTTP;
- regel 69: de inhoud van het antwoord;
- regel 70: headers die aan het antwoord moeten worden toegevoegd, hier geen;
- regel 73: er is een geldige actie. We vragen de (secundaire) controller om deze te verwerken;
- regel 74: we stellen de naam samen van de controllerklasse die moet worden uitgevoerd. [__NAMESPACE__] is de naamruimte waarin we ons bevinden, hier [Application] (regel 7);
- de namen van de secundaire controllerklassen staan in het bestand [config.json]:
"actions":
{
"init-session": "\\InitSessionController",
"authentifier-utilisateur": "\\AuthentifierUtilisateurController",
"calculer-impot": "\\CalculerImpotController",
"lister-simulations": "\\ListerSimulationsController",
"supprimer-simulation": "\\SupprimerSimulationController",
"fin-session": "\\FinSessionController",
"afficher-calcul-impot": "\\AfficherCalculImpotController"
},
Aan elke actie is een secundaire controller gekoppeld. Als de actie [authentifier-utilisateur] is, krijgt de variabele [$controller] op regel 74 dus de waarde [Application/AuthentifierUtilisateurController];
- regel 75: de naam van de secundaire controller wordt gelogd, ter controle tijdens de ontwikkeling;
- regel 76: de secundaire controller wordt uitgevoerd. We komen later nog terug op de secundaire controllers;
- regel 76: alle secundaire controllers leveren hetzelfde type resultaat op, namelijk een array:
- het eerste element van de array [$statusCode] is de statuscode HTTP van het te verzenden antwoord;
- het tweede element [$état] is de status van de applicatie na uitvoering van de controller;
- het derde element [$content] is een associatieve array met de enige sleutel [réponse], die de inhoud vormt van het antwoord dat naar de klant moet worden verzonden;
- het vierde element [$headers] is een array van headers HTTP die moeten worden toegevoegd aan het antwoord dat naar de klant wordt verzonden;
- regel 79: hier komen we terecht:
- ofwel omdat er een fout is opgetreden (regels 68-70);
- ofwel na het uitvoeren van een controller (regels 72-76);
- in beide gevallen zijn de [$statusCode, $état, $content, $headers]-elementen die nodig zijn voor het opstellen van het antwoord aan de klant bekend;
- regels 82-87: behandelen het specifieke geval van de statuscode [500 Internal Server Error]. Als een controller deze statuscode heeft ingesteld, betekent dit dat de applicatie niet kan functioneren. Dit is bijvoorbeeld het geval bij de belastingberekening als de gebruikte SGBD niet is gestart of niet meer reageert. Er wordt dan een e-mail naar de beheerder van de applicatie gestuurd om hem hiervan op de hoogte te stellen. We zullen niet specifiek ingaan op deze code. Het gebruik van de klasse [SendAdminMail] is al toegelicht (zie paragraaf met link);
- regels 89-95: het type [jSON, XML, HTML] van de webapplicatie wordt bepaald. Als de actie [init-session] met succes is uitgevoerd, bevindt dit type zich in de sessie die is gekoppeld aan de sleutel [type] (regel 91). Als dat niet het geval is, wordt er willekeurig een type voor het antwoord vastgesteld, namelijk het type jSON (regel 94);
- regel 97: [$content] is een array met één sleutel, [réponse], en één waarde, de inhoud van het antwoord dat naar de client moet worden verzonden. Hieraan worden de sleutels [action] en [état] toegevoegd. De sleutel [action] maakt het mogelijk om de logbestanden van het bestand [logs.txt] beter te volgen. De sleutel [état] heeft twee functies:
- hij stelt de clients jSON en XML in staat om te achterhalen in welke status de webapplicatie is terechtgekomen na het uitvoeren van de actie;
- in het geval van een antwoord HTML maakt hij het mogelijk om de weergave HTML te kiezen die naar de browser van de klant moet worden verzonden;
- regel 99: hier wordt het type klasse [Response] gekozen dat moet worden uitgevoerd om het antwoord naar de client te verzenden;
We hebben de klasse [JsonResponse] al besproken in de paragraaf over koppelingen. Deze implementeert de interface [InterfaceResponse] en is een uitbreiding van de klasse [ParentResponse]. Dit geldt ook voor de twee andere klassen [XmlResponse] en [HtmlResponse].
De antwoorden zijn verzameld in de map [Responses]:

Al deze klassen implementeren de interface [InterfaceResponse], die ook wordt beschreven in de paragraaf link:
<?php
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
interface InterfaceResponse {
// Verzoek $request: verzoek wordt verwerkt
// Sessie $session: de sessie van de webapplicatie
// array $config: de configuratie van de applicatie
// int statusCode: de statuscode van het antwoord
// array $content: het antwoord van de server
// array $headers: de headers HTTP die aan het antwoord moeten worden toegevoegd
// Logger $logger: de logger om logbestanden te schrijven
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void;
}
Deze interface heeft één enkele methode, [send], die verantwoordelijk is voor het verzenden van het antwoord naar de client. Deze methode heeft de 7 parameters die worden beschreven in de regels 11-17. Alle klassen en interfaces in de map [Responses] bevinden zich in de naamruimte [Application] (regel 3).
Laten we teruggaan naar de code van [main.php]:
…
// de sleutels [action, état] worden toegevoegd aan het antwoord van de controller
$content = ["action" => $action, "état" => $état] + $content;
// het object [Response] wordt geïnstantieerd om het antwoord naar de client te verzenden
$response = __NAMESPACE__ . $config["types"][$type];
(new $response())->send($request, $session, $config, $statusCode, $content, $headers, $logger);
// het antwoord is verzonden – de bronnen worden vrijgegeven
$logger->close();
exit;
- regel 5: we maken een instantie aan van de klasse [Response] die geschikt is voor het type van de toepassing. Deze klassen zijn als volgt gedefinieerd in het bestand [config.json]:
"types": {
"json": "\\JsonResponse",
"html": "\\HtmlResponse",
"xml": "\\XmlResponse"
},
- regel 5: de naam van de klasse wordt voorafgegaan door de naamruimte;
- regel 6: de klasse [Response] wordt geïnstantieerd en de methode [send] wordt aangeroepen met de 7 parameters die deze methode verwacht. Dit zijn de parameters van de interface [InterfaceResponse] die alle responsklassen implementeren. Hiermee wordt het antwoord naar de client verzonden;
- regel 9: het logbestand wordt gesloten;
- regel 10: de hoofdcontroller heeft zijn werk voltooid;
23.9.7. Tests [Postman] – 3
We gaan verschillende foutgevallen testen voor de parameter [action] van URL.

- in [1]:
- [erreur-101]: geval waarbij de parameter [action] ontbreekt in URL;
- [erreur-102]: het geval waarbij de parameter [action] wel aanwezig is in URL, maar niet wordt herkend;
- [erreur-103]: het geval waarbij de parameter [action] voorkomt in URL, herkend maar zonder dat het verwachte antwoordtype [json, xml, html] is gedefinieerd;
Elk verzoek wordt uitgevoerd. We presenteren direct de verkregen resultaten:
Hierboven:
- in [2-4], een verzoek zonder de parameter [action] in URL [4];
- in [5-7], het resultaat jSON;

Hierboven:
- in [5-9], een verzoek met een ongeldige parameter [action];
- in [10-13], het antwoord jSON;

Hierboven:
- in [14-19], een actie herkend maar het type (json, xml, html) is nog niet gespecificeerd;
- in [20-23], het antwoord jSON van de server;
23.10. De secundaire controllers
Elke actie wordt uitgevoerd door een van de controllers in de map [Controllers]:


In de algemene architectuur van de bovenstaande applicatie bevinden de secundaire controllers zich in [2a].
Elke controller implementeert de volgende interface [InterfaceController]:
<?php
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
interface InterfaceController {
// $config is de configuratie van de applicatie
// verwerking van een Request-verzoek
// gebruikt de sessie en kan deze wijzigen
// $infos zijn aanvullende gegevens die specifiek zijn voor elke controller
// geeft een tabel weer: [$statusCode, $état, $content, $headers]
public function execute(
array $config,
Request $request,
Session $session,
array $infos=NULL): array;
}
Opmerkingen
- alle secundaire controllers worden uitgevoerd via de methode [execute] op regel 17. Aan deze methode wordt de bekende informatie van de hoofdcontroller doorgegeven:
- regel 18: [array $config], die de configuratie van de applicatie bevat;
- regel 19: [Request $request], dit is de HTTP-aanvraag die momenteel wordt verwerkt;
- regel 20: [Session $session], de huidige sessie van de webapplicatie;
- regel 21: [array $infos=NULL], een extra array met informatie voor de controller voor het geval de eerste drie parameters van de methode niet voldoende zouden zijn. In deze applicatie is deze parameter nog nooit gebruikt. Hij is er uit voorzorg;
- regel 21: de methode [execute] retourneert de array [$statusCode, $état, $content, $headers]
- [int $statusCode]: de statuscode van het antwoord op HTTP;
- [int $état]: de toestand waarin de toepassing zich aan het einde van de uitvoering bevindt;
- [array $content]: een associatieve array [réponse=>résultat] waarbij [résultat] van willekeurig type is: dit is het door de controller gegenereerde resultaat dat naar de client wordt verzonden, nadat dit resultaat is geserialiseerd in de vorm van een tekenreeks;
- [array $headers]: de lijst met headers HTTP die moeten worden opgenomen in het antwoord HTTP van de server;
Elke secundaire controller wordt aangeroepen door de volgende code van de hoofdcontroller:
// de actie wordt uitgevoerd met behulp van de bijbehorende controller
$controller = __NAMESPACE__ . $config["actions"][$action];
list($statusCode, $état, $content, $headers) = (new $controller())->execute($config, $request, $session);
Op regel 3 zien we dat de 4e parameter [array $infos=NULL] van de methode [execute] niet wordt gebruikt.
23.11. De acties
We nemen nu de verschillende mogelijke acties van de webservice door:
Actie | Rol | Uitvoeringscontext |
init-session | Wordt gebruikt om het type (json, xml, html) van de gewenste antwoorden vast te leggen | Verzoek GET main.php?action=init-session&type=x kan op elk moment worden verzonden |
gebruikersauthenticatie | Geeft een gebruiker al dan niet toestemming om in te loggen | Verzoek POST main.php?action=authentifier-utilisateur Het verzoek moet twee POST-parameters bevatten [user, password] Kan alleen worden verzonden als het sessietype (json, xml, html) bekend is |
belasting-berekenen | Voert een simulatie van de belastingberekening uit | Verzoek POST main.php?action=belasting-berekenen De aanvraag moet drie POST-parameters bevatten: [marié, enfants, salaire] Kan alleen worden verzonden als het sessietype (json, xml, html) bekend is en de gebruiker is geauthenticeerd |
lister-simulations | Vraagt om de lijst met simulaties die sinds het begin van de sessie zijn uitgevoerd | Verzoek GET main.php?action=lister-simulations Het verzoek accepteert geen andere parameters Kan alleen worden verzonden als het type van de sessie (json, xml, html) bekend is en de gebruiker is geauthenticeerd |
simulatie-verwijderen | Verwijdert een simulatie uit de lijst met simulaties | Verzoek GET main.php?action=lister-simulations&nummer=x Het verzoek accepteert geen andere parameters Kan alleen worden verzonden als het type van de sessie (json, xml, html) bekend is en de gebruiker is geauthenticeerd |
sessie-beëindigen | Beëindigt de simulatiesessie. | Technisch gezien wordt de oude websessie verwijderd en wordt er een nieuwe sessie aangemaakt Kan alleen worden verzonden als het type van de sessie (json, xml, html) bekend is en de gebruiker is geauthenticeerd |
Alle secundaire controllers gaan op dezelfde manier te werk:
- ze controleren hun parameters. Deze zijn te vinden in het object [Request→query] voor de parameters die aanwezig zijn in het object URL en in het object [Request→request] voor de parameters die worden verzonden (verzoek POST);
- een controller lijkt op een functie of methode die de geldigheid van zijn parameters controleert. Voor de controller ligt het echter iets ingewikkelder:
- de verwachte parameters kunnen ontbreken;
- de verwachte parameters zijn allemaal tekenreeksen, terwijl een functie het type van haar parameters kan vastleggen. Als de verwachte parameter een getal is, moet worden gecontroleerd of de tekenreeks van de parameter inderdaad die van een getal is;
- zodra is gecontroleerd dat de verwachte parameters aanwezig en syntactisch correct zijn, moet worden gecontroleerd of ze geldig zijn in de huidige uitvoeringscontext. Deze context is aanwezig in de sessie. Het voorbeeld van authenticatie is een voorbeeld van een uitvoeringscontext. Bepaalde acties mogen pas worden verwerkt nadat de klant is geauthenticeerd. Meestal geeft een sleutel in de sessie aan of deze authenticatie heeft plaatsgevonden of niet;
- zodra de voorgaande controles zijn uitgevoerd, kan de secundaire controller aan de slag. Dit controleren van de parameters is van groot belang. We kunnen niet accepteren dat een klant ons op elk willekeurig moment tijdens de levensduur van de applicatie zomaar iets stuurt. We moeten de volledige controle hebben over de levensduur ervan;
- zodra zijn taak is voltooid, retourneert de secundaire controller de tabel [$statusCode, $état, $content, $headers] die wordt verwacht door de primaire controller die hem heeft aangeroepen;
We gaan nu de verschillende controllers doornemen, of, wat op hetzelfde neerkomt, de verschillende acties die de levenscyclus van de webapplicatie bepalen.
23.11.1. De actie [init-session]
De actie [init-session] wordt verwerkt door de volgende controller [InitSessionController]:
<?php
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
class InitSessionController implements InterfaceController {
// $config is de configuratie van de applicatie
// verwerking van een Request-verzoek
// maakt gebruik van de sessie en kan deze wijzigen
// $infos zijn aanvullende gegevens die specifiek zijn voor elke controller
// geeft een array terug: [$statusCode, $état, $content, $headers]
public function execute(
array $config,
Request $request,
Session $session,
array $infos = NULL): array {
// moet een GET en één enkele parameter anders dan [action] bevatten
$method = strtolower($request->getMethod());
$erreur = $method !== "get" || $request->query->count() != 2;
if ($erreur) {
$état = 701;
$message = "méthode GET exigée avec paramètres [action, type] dans l'URL";
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// we halen de parameters op van GET
$erreur = FALSE;
// type
if (!$request->query->has("type")) {
$erreur = TRUE;
$état = 702;
$message = "paramètre [type] manquant";
} else {
$type = strtolower($request->query->get("type"));
}
// type controle
if (!$erreur && !array_key_exists($type, $config["types"])) {
$erreur = TRUE;
$état = 703;
$message = "paramètre type [$type] invalide";
}
// fout?
if ($erreur) {
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// het sessietype wordt in de sessie opgeslagen
$session->set("type", $type);
// succesmelding
$message = "session démarrée avec type [$type]";
$état = 700;
return [Response::HTTP_OK, $état, ["réponse" => $message], []];
}
}
Opmerkingen
- er wordt gewacht op een verzoek [GET main.php?action=init-session&type=xxx]
- regels 25-26: er wordt gecontroleerd of het verzoek een verzoek GET is met twee parameters in de URL;
- regels 27-31: als dat niet het geval is, wordt de fout geregistreerd en wordt een resultaat [$statusCode, $état, $content, $headers] naar de hoofdcontroller verzonden;
- regels 35-39: er wordt gecontroleerd of de parameter [type] wel degelijk aanwezig is in de URL. Als dat niet het geval is, wordt de fout genoteerd;
- regel 40: het type van de sessie wordt genoteerd;
- regels 43-47: er wordt gecontroleerd of het type van de sessie een van de termen (json, xml, html) is. Als dit niet het geval is, wordt de fout genoteerd;
- regels 49-51: als er een fout is opgetreden, sturen we een resultaat [$statusCode, $état, $content, $headers] naar de hoofdcontroller;
- regel 53: het type van de sessie wordt in de sessie van de webapplicatie opgeslagen;
- regels 55-57: de controller heeft zijn taak voltooid. Er wordt een succesresultaat [$statusCode, $état, $content, $headers] naar de hoofdcontroller verzonden;
Laten we nog eens bekijken wat de hoofdcontroller doet met het antwoord van de secundaire controllers:
// fouten?
if ($erreurs) {
// het antwoord wordt voorbereid zonder het te verzenden
$statusCode = Response::HTTP_BAD_REQUEST;
$content = ["réponse" => $erreurs];
$headers = [];
} else {
// ---------------------------
// de actie wordt uitgevoerd met behulp van de bijbehorende controller
$controller = __NAMESPACE__ . $config["actions"][$action];
$logger->write("contrôleur : $controller\n");
list($statusCode, $état, $content, $headers) = (new $controller())->execute($config, $request, $session);
}
// --------------------- het antwoord wordt verzonden
// in geval van een fatale fout HTTP_INTERNAL_SERVER_ERROR
// er wordt een e-mail naar de beheerder gestuurd indien mogelijk
if ($statusCode === Response::HTTP_INTERNAL_SERVER_ERROR && $config['adminMail'] != NULL) {
$infosMail = $config['adminMail'];
$infosMail['message'] = json_encode($content, JSON_UNESCAPED_UNICODE);
$sendAdminMail = new SendAdminMail($infosMail, $logger);
$sendAdminMail->send();
}
// het antwoord hangt af van het type sessie
if ($session->has("type")) {
// het sessietype staat in de sessie
$type = $session->get("type");
} else {
// als er geen type in de sessie staat, dan is het standaardantwoord in jSON
$type = "json";
}
// de sleutels [action, état] worden aan het antwoord van de controller toegevoegd
$content = ["action" => $action, "état" => $état] + $content;
// we maken een instantie aan van het object [Response] dat verantwoordelijk is voor het verzenden van het antwoord naar de client
$response = __NAMESPACE__ . $config["types"][$type]["response"];
(new $response())->send($request, $session, $config, $statusCode, $content, $headers, $logger);
// het antwoord is verzonden – de bronnen worden vrijgegeven
$logger->close();
exit;
- regel 12: de hoofdcontroller haalt het resultaat van de secundaire controller op;
- regels 35-36: na enkele controles verstuurt het het antwoord door een van de klassen [JsonResponse, XmlResponse, HtmlResponse] te instantiëren, afhankelijk van het type (json, xml, html) van de huidige sessie;
Vervolgens zullen we [Postman]-tests uitvoeren in het kader van een simulatiesessie met het type [json]. De werking van de klasse [JsonResponse] is beschreven in de paragraaf met de link.
23.11.2. Tests [Postman]

Hierboven:
- in [2], drie nieuwe tests;
- in [3-7], de actie [init-session] waarbij de parameter [type] ontbreekt;
- in [8-11], het antwoord jSON van de server;

Hierboven:
- in [1-7], de actie [init-session] met een onjuiste parameter [type];
- in [8-11], het antwoord jSON van de server;

Hierboven:
- in [1-8], de actie [init-session] met het type jSON;
- in [9-12], het antwoord jSON van de server;
23.11.3. De actie [authentifier-utilisateur]
De actie [authentifier-utilisateur] wordt uitgevoerd door de volgende controller [AuthentifierUtilisateurController]:
<?php
namespace Application;
// Symfony-afhankelijkheden
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
class AuthentifierUtilisateurController implements InterfaceController {
// $config is de configuratie van de applicatie
// verwerking van een Request-verzoek
// gebruikt de sessie en kan deze wijzigen
// $infos zijn aanvullende gegevens die specifiek zijn voor elke controller
// geeft een array terug: [$statusCode, $état, $content, $headers]
public function execute(
array $config,
Request $request,
Session $session,
array $infos = NULL): array {
// moet een POST en één enkele parameter GET bevatten
$method = strtolower($request->getMethod());
$erreur = $method !== "post" || $request->query->count() != 1;
if ($erreur) {
$état = 201;
$message = "méthode POST requise, paramètre [action] dans l'URL, paramètres postés [user,password]";
// het resultaat wordt teruggestuurd naar de hoofdcontroller
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// we halen de parameters op uit de POST
$erreurs = [];
// gebruiker
$état = 210;
if (!$request->request->has("user")) {
$état += 2;
$erreurs[] = "paramètre [user] manquant";
} else {
$user = $request->request->get("user");
}
// wachtwoord
if (!$request->request->has("password")) {
$état += 4;
$erreurs[] = "paramètre [password] manquant";
} else {
$password = trim($request->request->get("password"));
}
// fout?
if ($erreurs) {
// het resultaat wordt teruggestuurd naar de hoofdcontroller
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $erreurs], []];
}
// controle van de gebruikersgegevens
// bestaat de gebruiker?
$users = $config["users"];
$i = 0;
$trouvé = FALSE;
while (!$trouvé && $i < count($users)) {
$trouvé = ($user === $users[$i]["login"] && $users[$i]["passwd"] === $password);
$i++;
}
// gevonden?
if (!$trouvé) {
// foutmelding
$message = "Echec de l'authentification [$user, $password]";
$état = 221;
// het resultaat wordt teruggestuurd naar de hoofdcontroller
return [Response::HTTP_UNAUTHORIZED, $état, ["réponse" => $message], []];
} else {
// in de sessie wordt genoteerd dat de gebruiker is geauthenticeerd
$session->set("user", TRUE);
// succesmelding
$message = "Authentification réussie [$user, $password]";
$état = 200;
// het resultaat wordt teruggestuurd naar de hoofdcontroller
return [Response::HTTP_OK, $état, ["réponse" => $message], []];
}
}
}
Opmerkingen
- er wordt gewacht op een verzoek [POST main.php?action=authentifier-utilisateur] met twee verzonden parameters [user, password];
- regels 24-25: er wordt gecontroleerd of er een verzoek POST is met één enkele parameter in URL;
- regels 26-31: als er een fout is, wordt deze genoteerd en wordt een resultaat [$statusCode, $état, $content, $headers] teruggestuurd naar de hoofdcontroller;
- regels 36-39: we controleren of de parameter [user] in de verzonden waarden aanwezig is. Als deze niet aanwezig is, noteren we de fout;
- regels 43-45: er wordt gecontroleerd of de parameter [password] in de verzonden waarden aanwezig is. Als deze niet aanwezig is, wordt de fout genoteerd;
- regels 50-53: als een van de doorgegeven waarden ontbreekt, wordt een resultaat [$statusCode, $état, $content, $headers] teruggestuurd naar de hoofdcontroller;
- regels 56-62: er wordt gecontroleerd of het opgehaalde paar [$user,$password] aanwezig is in de tabel [$config[‘users’]] van het configuratiebestand;
- regels 64-69: als dat niet het geval is, wordt de fout geregistreerd. De statuscode HTTP wordt gewijzigd in [Response::HTTP_UNAUTHORIZED] en het resultaat [$statusCode, $état, $content, $headers] wordt teruggestuurd naar de hoofdcontroller;
- regel 72: de authenticatie is geslaagd. Dit wordt in de sessie vastgelegd door de sleutel [user] in de sessie op te nemen. De aanwezigheid van deze sleutel geeft aan dat de authenticatie is geslaagd;
- regels 73-77: er wordt een succesresultaat [$statusCode, $état, $content, $headers] teruggestuurd naar de hoofdcontroller;
23.11.4. Tests [Postman]
We voeren de [Postman]-tests uit op de controller [AuthentifierUtilisateurController] in de modus jSON;

Hierboven:
- in [1-6], de actie [authentifier-utilisateur] met een GET [2], terwijl een POST vereist is;
- in [7-10], het antwoord jSON van de server;
Laten we de GET vervangen door een POST [2] zonder parameters in de hoofdtekst van het antwoord [7] op te nemen:

Hierboven:
- in [1-7], de POST zonder verzonden parameters in [7];
- in [8-11], het antwoord jSON van de server;
Laten we nu een parameter [password] toevoegen aan de body (body) [4] van het verzoek:

Hierboven:
- in [1-6], een verzoek POST [2] met een parameter [password] die is verzonden via [4-6]. De verzonden parameters moeten worden toegevoegd aan de body van het verzoek [4]. Er zijn verschillende manieren om waarden naar de server te verzenden. We kiezen voor de methode [x-www-form-urlencoded] [5];
- in [8-10], het antwoord jSON van de server;
Laten we nu de parameter [user] definiëren zonder de parameter [password]:

Hierboven:
- in [1-7], een verzoek POST zonder de parameter [password] [4-7];
- in [8-11], het antwoord jSON van de server;
Laten we nu de twee verzonden parameters [user, password] definiëren, maar met waarden die ervoor zorgen dat de authenticatie mislukt:

Hierboven:
- in [1-9], een verzoek POST met onjuiste verzonden parameters [user, password];
- in [10-13], het antwoord jSON van de server. Let op de statuscode [401 Unauthorized] [10] van het antwoord;
Nu een verzoek POST met geldige inloggegevens:

Hierboven:
- in [1-9], het verzoek POST [2] met geldige inloggegevens [6-9];
- in [10-13], het antwoord jSON van de server. Let op de statuscodes HTTP, [200 OK] en [10];
23.11.5. De actie [calculer-impot]
De actie [calculer-impot] wordt verwerkt door de volgende controller [CalculerImpotController]:
<?php
namespace Application;
// Symfony-afhankelijkheden
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
// alias van de laag [dao]
use \Application\ServerDaoWithSession as ServerDaoWithRedis;
class CalculerImpotController implements InterfaceController {
// $config is de configuratie van de applicatie
// verwerking van een Request-verzoek
// maakt gebruik van de sessie Session en kan deze wijzigen
// $infos zijn aanvullende gegevens die specifiek zijn voor elke controller
// geeft een array terug: [$statusCode, $état, $content, $headers]
public function execute(
array $config,
Request $request,
Session $session,
array $infos = NULL): array {
// moet een parameter GET en drie parameters POST bevatten
$method = strtolower($request->getMethod());
$erreur = $method !== "post" || $request->query->count() != 1;
if ($erreur) {
// er wordt een fout gemeld
$message = "il faut utiliser la méthode [post] avec [action] dans l'URL et les paramètres postés [marié, enfants, salaire]";
$état = 301;
// het resultaat wordt teruggestuurd naar de hoofdcontroller
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// de parameters van POST worden opgehaald
$erreurs = [];
$état = 310;
// burgerlijke staat
if (!$request->request->has("marié")) {
$état += 2;
$erreurs[] = "paramètre [marié] manquant";
} else {
$marié = trim(strtolower($request->request->get("marié")));
$erreur = $marié !== "oui" && $marié !== "non";
if ($erreur) {
$état += 4;
$erreurs[] = "valeur [$marié] invalide pour le paramètre [marié]";
}
}
// het aantal kinderen wordt opgehaald
if (!$request->request->has("enfants")) {
$état += 8;
$erreurs[] = "paramètre [enfants] manquant";
} else {
$enfants = trim($request->request->get("enfants"));
$erreur = !preg_match("/^\d+$/", $enfants);
if ($erreur) {
$état += 9;
$erreurs[] = "valeur [$enfants] invalide pour le paramètre [enfants]";
}
}
// het jaarsalaris wordt opgehaald
if (!$request->request->has("salaire")) {
$erreurs[] = "paramètre [salaire] manquant";
$état += 16;
} else {
$salaire = trim($request->request->get("salaire"));
$erreur = !preg_match("/^\d+$/", $salaire);
if ($erreur) {
$état += 17;
$erreurs[] = "valeur [$salaire] invalide pour le paramètre [salaire]";
}
}
// fout?
if ($erreurs) {
// het resultaat wordt teruggestuurd naar de hoofdcontroller
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $erreurs], []];
}
// we hebben alles wat we nodig hebben om aan de slag te gaan
// Redis
\Predis\Autoloader::register();
try {
// client [predis]
$redis = new \Predis\Client();
// we maken verbinding met de server om te kijken of hij er is
$redis->connect();
} catch (\Predis\Connection\ConnectionException $ex) {
// het is misgegaan
// het resultaat met de fout wordt teruggestuurd naar de hoofdcontroller
$état = 350;
return [Response::HTTP_INTERNAL_SERVER_ERROR, $état,
["réponse" => "[redis], " . utf8_encode($ex->getMessage())], []];
}
// we hebben geldige parameters
// aanmaken van de laag [dao]
if (!$redis->get("taxAdminData")) {
try {
// we halen de fiscale gegevens op uit de database
$dao = new ServerDaoWithRedis($config["databaseFilename"], NULL);
// de opgehaalde gegevens worden in Redis opgeslagen
$redis->set("taxAdminData", $dao->getTaxAdminData());
} catch (\RuntimeException $ex) {
// er is iets misgegaan
// het resultaat wordt met een foutmelding teruggestuurd naar de hoofdcontroller
$état = 340;
return [Response::HTTP_INTERNAL_SERVER_ERROR, $état,
["réponse" => utf8_encode($ex->getMessage())], []];
}
} else {
// de belastinggegevens worden opgeslagen in het werkgeheugen [application]
$arrayOfAttributes = \json_decode($redis->get("taxAdminData"), true);
$taxAdminData = (new TaxAdminData())->setFromArrayOfAttributes($arrayOfAttributes);
// instantie van de laag [dao]
$dao = new ServerDaoWithRedis(NULL, $taxAdminData);
}
// aanmaken van de laag [métier]
$métier = new ServerMetier($dao);
// we hebben alles wat we nodig hebben om aan de slag te gaan – berekening van de belasting
$résultat = $métier->calculerImpot($marié, (int) $enfants, (int) $salaire);
// we voegen de zojuist uitgevoerde simulatie toe aan de sessie
$simulation = new Simulation();
$résultat = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $résultat;
$simulation->setFromArrayOfAttributes($résultat);
// is er een lijst met simulaties in de sessie?
if (!$session->has("simulations")) {
$simulations = [];
} else {
$simulations = $session->get("simulations");
}
// de simulatie toevoegen aan de lijst met simulaties
$simulations[] = $simulation;
// de simulaties worden weer in de sessie geplaatst
$session->set("simulations", $simulations);
// het resultaat wordt teruggestuurd naar de hoofdcontroller
$état = 300;
return [Response::HTTP_OK, $état, ["réponse" => $résultat], []];
}
}
Opmerkingen
- de verwachte aanvraag is [POST main.php?action=calculer-impot] met drie verzonden parameters [marié, enfants, salaire]:
- [marié] moet een waarde hebben die is opgeslagen in [oui, non];
- [enfants, salaire] moeten positieve gehele getallen of nul zijn;
- regels 26-27: er wordt gecontroleerd of er inderdaad een POST is met één enkele parameter in URL;
- regels 28-34: als dat niet het geval is, wordt een foutmelding naar de hoofdcontroller gestuurd;
- regel 36: de foutmeldingen worden verzameld in de tabel [$erreurs];
- regels 39-41: we controleren of de parameter [marié] aanwezig is. Als deze niet aanwezig is, wordt de fout geregistreerd;
- regels 43-49: er wordt gecontroleerd of de waarde van [marié] voorkomt in [oui, non]. Als dat niet het geval is, wordt de fout genoteerd;
- regels 51-54: er wordt gecontroleerd of de parameter [enfants] aanwezig is. Als deze niet aanwezig is, wordt de fout genoteerd;
- regels 55-61: er wordt gecontroleerd of de waarde van de parameter [enfants] een positief getal of nul is. Als dat niet het geval is, wordt de fout genoteerd;
- regels 63-66: er wordt gecontroleerd of de parameter [salaire] aanwezig is. Als deze niet aanwezig is, wordt de fout geregistreerd;
- regels 67-72: er wordt gecontroleerd of de waarde van de parameter [salaire] een positief getal of nul is. Als dat niet het geval is, wordt de fout genoteerd;
- regels 75-78: als de tabel [$erreurs] niet leeg is, zijn er fouten opgetreden. De foutentabel wordt in het antwoord opgenomen en het resultaat wordt teruggestuurd naar de hoofdcontroller;
- regel 80: we hebben geldige parameters. We kunnen de belasting berekenen. Hiervoor moeten de lagen [dao] en [métier] worden opgebouwd, die deze berekening kunnen uitvoeren;
- regels 82-94: we maken een client [Redis] aan;
- regels 88-94: als er geen verbinding kon worden gemaakt met de server [Redis], wordt een code [500 Internal Server Error] naar de klant gestuurd;
- regel 98: er wordt gecontroleerd of de server [Redis] de sleutel [taxAdminData] heeft. Deze sleutel vertegenwoordigt de gegevens van de belastingdienst. Als de sleutel niet aanwezig is, moeten de belastinggegevens uit de database worden opgehaald;
- regel 101: aanmaken van de laag [dao] wanneer de belastinggegevens uit de database moeten worden opgehaald. De klasse [ServerDaoWithRedis] is beschreven in de paragraaf ‘koppeling’;
- regel 103: de uit de database opgehaalde gegevens worden opgeslagen in het geheugen [Redis] met de sleutel [taxAdminData];
- regels 104-110: als de zoekopdracht in de database is mislukt, wordt de door de laag [dao] geretourneerde fout genoteerd en opgenomen in het resultaat dat naar de hoofdcontroller wordt teruggestuurd;
- regel 109: het foutbericht dat door de laag [PDO] wordt teruggestuurd, is gecodeerd in [iso-8859-1]. Dit wordt gecodeerd in [utf-8];
- regels 111-117: als de sleutel [taxAdminData] bestaat in het geheugen [Redis], dan worden de fiscale gegevens rechtstreeks doorgegeven aan de constructor van de laag [dao];
- regel 119: de laag [métier] wordt aangemaakt. De klasse [ServerMetier] is beschreven in de paragraaf ‘koppeling’;
- regels 124-126: met het berekende belastingbedrag wordt een object [Simulation] aangemaakt. De klasse [Simulation] omvat de gegevens van een simulatie en is beschreven in de paragraaf ‘link’;
- regels 128-132: de zojuist opgebouwde simulatie moet worden toegevoegd aan de lijst met reeds berekende simulaties. Deze lijst bevindt zich in de sessie, tenzij er nog geen simulaties zijn uitgevoerd;
- regels 133-136: de simulatie wordt toegevoegd aan de lijst met simulaties en deze lijst wordt opnieuw in de sessie opgeslagen;
- regels 137-139: het resultaat wordt teruggestuurd naar de hoofdcontroller;
23.11.6. Tests [Postman]
We voeren de [Postman]-tests uit op de controller [CalculerImpotController] in de modus jSON;

Hierboven:
- in [1-7] wordt een verzoek [GET] verzonden in plaats van [POST];
- in [8-11] is het antwoord jSON van de server;
Laten we nu een methode [POST] gebruiken, met of zonder geposte parameters en ook met ongeldige geposte parameters:

Hierboven:
- voeren we een verzoek uit met [POST] [2] met ongeldige POST-parameters [6-11] [marié, enfants, salaire]. U kunt een van deze parameters weglaten door het selectievakje in [16] uit te vinken. Zo kunt u verschillende scenario’s testen. Op de bovenstaande schermafbeelding zijn de drie parameters aanwezig en allemaal ongeldig;
- in [12-15] is het antwoord van de server jSON;
Laten we nu twee van de drie verzonden parameters uitschakelen:

Hierboven,
- in [5-8] wordt alleen de parameter [salaire] verzonden en bovendien is deze ongeldig;
- in [9-11] is het resultaat jSON van de server;
Laten we nu een belastingberekening maken met geldige parameters:

Hierboven:
- in [1118], een verzoek met geldige parameters [6-8];
- in [12-14], het antwoord jSON van de server;
23.11.7. De actie [lister-simulations]
De actie [lister-simulations] wordt verwerkt door de volgende secundaire controller [ListerSimulationsController]:
<?php
namespace Application;
// Symfony-afhankelijkheden
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
class ListerSimulationsController {
// $config is de configuratie van de applicatie
// verwerking van een Request-verzoek
// maakt gebruik van de sessie en kan deze wijzigen
// $infos zijn aanvullende gegevens die specifiek zijn voor elke controller
// geeft een array terug: [$statusCode, $état, $content, $headers]
public function execute(
array $config,
Request $request,
Session $session,
array $infos = NULL): array {
// er moet slechts één parameter zijn: GET
$method = strtolower($request->getMethod());
$erreur = $method !== "get" || $request->query->count() != 1;
if ($erreur) {
$état = 501;
$message = "GET requis, avec l'unique paramètre [action] dans l'URL";
// er wordt een resultaat met een fout teruggestuurd naar de hoofdcontroller
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// de lijst met simulaties in de sessie wordt opgehaald
if (!$session->has("simulations")) {
$simulations = [];
} else {
$simulations = $session->get("simulations");
}
// er wordt een succesvol resultaat teruggestuurd naar de hoofdcontroller
$état = 500;
return [Response::HTTP_OK, $état, ["réponse" => $simulations], []];
}
}
Opmerkingen
- verzoek [GET main.php?action=lister-simulations];
- regels 24-25: er wordt gecontroleerd of er een verzoek GET is met één enkele parameter;
- regels 26-31: als dat niet het geval is, wordt een resultaat met een fout teruggestuurd naar de hoofdcontroller;
- regels 33-37: de lijst met simulaties wordt uit de sessie opgehaald als deze daar aanwezig is (regel 36), anders is deze lijst leeg (regel 34);
- regels 39-40: de lijst met simulaties wordt teruggestuurd naar de hoofdcontroller;
23.11.8. Tests [Postman]
We gaan twee tests maken: een fouttest en een succesvolle test.

Hierboven:
- in [1-8] voeren we een verzoek [GET] uit met een overbodige parameter [param1] in de URL [3, 7-8];
- in [9-12], het antwoord jSON van de server;
Laten we nu een geldig verzoek doen:

Hierboven:
- in [1-5], een geldig verzoek;
Het resultaat van het verzoek is als volgt:

- in [3-6], het antwoord jSON van de server. Voorafgaand aan deze test was de test [Postman] [calculer-impot-300] meerdere keren uitgevoerd om simulaties in de websessie van de server te creëren;
23.11.9. De actie [supprimer-simulation]
De actie [supprimer-simulation] wordt verwerkt door de volgende secundaire controller [SupprimerSessionController]:
<?php
namespace Application;
// Symfony-afhankelijkheden
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
class SupprimerSimulationController {
/// $config is de configuratie van de applicatie
// verwerking van een Request-verzoek
// maakt gebruik van de sessie en kan deze wijzigen
// $infos zijn aanvullende gegevens die specifiek zijn voor elke controller
// geeft een array terug: [$statusCode, $état, $content, $headers]
public function execute(
array $config,
Request $request,
Session $session,
array $infos = NULL): array {
// er moeten twee parameters zijn: GET
$method = strtolower($request->getMethod());
$erreur = $method !== "get" || $request->query->count() != 2;
$état = 600;
if ($erreur) {
$état += 2;
$message = "GET requis, avec les paramètres [action, numéro]";
}
// de parameter [numéro] moet bestaan
if (!$erreur) {
$état += 4;
$erreur = !$request->query->has("numéro");
if ($erreur) {
$message = "paramètre [numéro] manquant";
}
}
// de parameter [numéro] moet geldig zijn
if (!$erreur) {
$état += 8;
$numéro = $request->query->get("numéro");
$erreur = !preg_match("/^\d+$/", $numéro);
if ($erreur) {
$message = "paramètre [$numéro] invalide";
}
}
// de parameter [numéro] moet binnen het interval [0,n-1] liggen
// als n het aantal simulaties is
if (!$erreur) {
$numéro = (int) $numéro;
$erreur = !$session->has("simulations");
if (!$erreur) {
$simulations = $session->get("simulations");
$erreur = $numéro < 0 || $numéro >= count($simulations);
}
if ($erreur) {
$état += 16;
$message = "la simulation n° [$numéro] n'existe pas";
}
}
// fout?
if ($erreur) {
// het resultaat wordt teruggestuurd naar de hoofdcontroller
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// de simulatie wordt verwijderd $numéro
unset($simulations[$numéro]);
$simulations = array_values($simulations);
// de simulaties worden weer in de sessie geplaatst
$session->set("simulations", $simulations);
// de lijst met simulaties wordt teruggestuurd naar de client
$état = 600;
return [Response::HTTP_OK, $état, ["réponse" => $simulations], []];
}
}
Opmerkingen
- verzoek [GET main.php?action=supprimer-simulation&numéro=x];
- regels 24-30: er wordt gecontroleerd of er een verzoek GET met twee parameters is;
- regels 32-38: er wordt gecontroleerd of de parameter [numéro] voorkomt in de parameters van URL;
- regels 40-47: er wordt gecontroleerd of de waarde van de parameter [numéro] syntactisch correct is;
- regels 50-61: er wordt gecontroleerd of simulatie nr. [numéro] daadwerkelijk bestaat. Er zijn twee foutgevallen:
- de lijst met simulaties kan niet worden gevonden in de sessie (regel 52);
- het nummer [numéro] van de te verwijderen simulatie komt niet voor in de lijst met simulaties;
- regels 63-66: in geval van een fout wordt een foutmelding teruggestuurd naar de hoofdcontroller;
- regel 68: simulatie nr. [numéro] wordt verwijderd;
- regel 69: de bewerking [unset] wijzigt de indexen [0, n-1] in de lijst niet. Om deze bij te werken, worden de waarden van de tabel [$simulations] opgevraagd om de ontbrekende simulatie te verwijderen;
- regel 71: de nieuwe tabel met simulaties wordt teruggeplaatst in de sessie;
- regels 73-74: de nieuwe lijst met simulaties wordt teruggestuurd naar de hoofdcontroller;
23.11.10. Tests [Postman]
We gaan fout- en succes-tests uitvoeren:

Hierboven:
- in [1-6], een verzoek GET zonder de parameter [numéro];
- in [7-10], het antwoord jSON van de server;
Nu een verzoek met een syntactisch onjuist nummer:

Hierboven:
- in [1-5], een verzoek GET met een ongeldige parameter [numéro] [3, 5];
- in [6-9], het antwoord jSON van de server;
Nu een verzoek met een simulatienummer dat niet bestaat:

Hierboven:
- in [1-5], een verzoek met een simulatienummer gelijk aan 100 dat niet voorkomt in de lijst met simulaties;
- in [6-9], het antwoord jSON van de server;
Nu gaan we simulatie nr. 0 uit de lijst verwijderen, dus de eerste simulatie. Laten we eerst deze lijst opnieuw opvragen met de aanvraag [lister-simulations-500]:

- In [1] zijn er momenteel 2 simulaties;
We verwijderen de eerste simulatie (nummer 0):

Hierboven:
- in [1-5] verwijderen we simulatie nr. 0 [5];
- in [6-9] is het antwoord jSON van de server. We zien dat simulatie nr. 0 is verwijderd;
Laten we deze handeling herhalen:

Hierboven:
- in [1] zijn er geen simulaties meer over in de websessie van de server;
23.11.11. De actie [fin-session]
De actie [fin-session] wordt verwerkt door de volgende secundaire controller [FinSessionController]:
<?php
namespace Application;
// Symfony-afhankelijkheden
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
class FinSessionController implements InterfaceController {
// $config is de configuratie van de applicatie
// verwerking van een Request-verzoek
// gebruikt de sessie en kan deze wijzigen
// $infos zijn aanvullende gegevens die specifiek zijn voor elke controller
// geeft een array terug: [$statusCode, $état, $content, $headers]
public function execute(
array $config,
Request $request,
Session $session,
array $infos = NULL): array {
// er moet slechts één parameter zijn: GET
$method = strtolower($request->getMethod());
$erreur = $method !== "get" || $request->query->count() != 1;
// fout?
if ($erreur) {
$état = 401;
// resultaat naar de hoofdcontroller
$message = "GET requis avec le seul paramètre [action] dans l'URL";
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// het sessietype wordt opgeslagen
$type = $session->get("type");
// de huidige sessie wordt ongeldig gemaakt
$session->invalidate();
// het type wordt opnieuw ingesteld in de nieuwe sessie
$session->set("type", $type);
// het antwoord wordt verzonden
$état = 400;
// resultaat naar de hoofdcontroller
$content = ["réponse" => "session supprimée"];
return [Response::HTTP_OK, $état, $content, []];
}
}
Opmerkingen
- verzoek [GET main.php?action=fin-session];
- regels 25-33: er wordt gecontroleerd of de actie een GET is met de enige parameter [fin-action];
- regel 38: de huidige sessie wordt ongeldig gemaakt. Hierdoor worden de daarin opgeslagen gegevens gewist en wordt een nieuwe sessie gestart;
- regel 36: vóór het einde van de sessie wordt het type [json, xml, html] van de sessie opgeslagen;
- regel 40: het type van de vorige sessie wordt in de nieuwe sessie teruggeplaatst. Uiteindelijk wordt er verdergegaan met een nieuwe sessie met de unieke sleutel [type];
- regels 44-45: het resultaat wordt teruggestuurd naar de hoofdcontroller;
23.11.12. Tests [Postman]
We gaan een fouttest en een succesvolle test uitvoeren:

Hierboven:
- in [1-5] vragen we om het beëindigen van de sessie [5] met een POST [2] in plaats van de verwachte GET;
- in [6-9], het antwoord jSON van de server;
Nu een voorbeeld van een geslaagde test. Laten we eerst eens kijken naar de sessiecookie die tijdens de laatste test tussen de client [Postman] en de server is uitgewisseld:

Hierboven:
- in [3], de sessiecookie die door de client [Postman] naar de server is verzonden;
Laten we nu eens kijken naar de headers HTTP die door de server in zijn antwoord zijn verzonden:

Hierboven:
- in [3-4] ontbreekt het sessiecookie in het antwoord van de server. Dat is normaal. De server verstuurt het slechts één keer: aan het begin van een nieuwe websessie;
Laten we nu een geldige [fin-session]-actie uitvoeren:

Hierboven:
- in [1-3], een geldige actie [fin-session];
- in [4-7], het antwoord jSON van de server;
Laten we eens kijken naar de headers HTTP die in het antwoord van de server zijn verzonden:

- in [3] verstuurt de server de header [Set-Cookie], waarmee wordt aangegeven dat er een nieuwe websessie begint;
23.12. Soorten reacties van de server
23.12.1. Inleiding
Laten we nog eens terugkomen op de algemene architectuur van de applicatie:

We zullen de mogelijke antwoordtypes [3a] toelichten. Deze zijn verzameld in de map [Responses] van het project:

We hebben de klasse [JsonResponse] al besproken in de paragraaf over links. Deze implementeert de interface [InterfaceResponse] en is een uitbreiding van de klasse [ParentResponse]. Dit geldt ook voor de twee andere klassen [XmlResponse] en [HtmlResponse].
Laten we de definitie van de interface [InterfaceResponse] nog eens bekijken:
<?php
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
interface InterfaceResponse {
// Verzoek $request: verzoek wordt verwerkt
// Sessie $session: de sessie van de webapplicatie
// array $config: de configuratie van de applicatie
// int statusCode: de statuscode van het antwoord
// array $content: het antwoord van de server
// array $headers: de headers HTTP die aan het antwoord moeten worden toegevoegd
// Logger $logger: de logger om logbestanden te schrijven
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void;
}
- regels 19-27: de interface [InterfaceResponse] heeft één enkele methode, [send], om het antwoord naar de client te verzenden;
- regels 11-17: de betekenis van de verschillende parameters van de methode [send];
- regels 23-25: de parameters [$statusCode, $content, $headers] vormen het standaardantwoord van de secundaire controllers van de applicatie. Het antwoord kan echter aanvullende informatie nodig hebben. Daarom worden de eerste drie parameters (regels 20-22) meegegeven, die toegang geven tot alle informatie over het verzoek, de sessie en de configuratie;
- regel 26: het antwoord heeft de parameter [Logger] nodig omdat het het naar de klant verzonden antwoord gaat loggen;
Laten we nu nog eens kijken naar de code van de klasse [ParentResponse], de bovenliggende klasse van de drie antwoordtypen die de gemeenschappelijke elementen samenvat: het daadwerkelijk verzenden van een tekstantwoord naar de klant:
<?php
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Response;
class ParentResponse {
// int $statusCode: de statuscode HTTP van het antwoord
// string $content: de inhoud van het te verzenden antwoord
// afhankelijk van het geval is dit een tekenreeks jSON, XML of HTML
// array $headers: de headers HTTP die aan het antwoord moeten worden toegevoegd
public function sendResponse(
int $statusCode,
string $content,
array $headers): void {
// voorbereiding van het tekstantwoord van de server
$response = new Response();
$response->setCharset("utf-8");
// statuscode
$response->setStatusCode($statusCode);
// headers
foreach ($headers as $text => $value) {
$response->headers->set($text, $value);
}
// het antwoord wordt verzonden
$response->setContent($content);
$response->send();
}
}
Opmerkingen
- regels 10-13: de betekenis van de drie parameters van de methode [send];
- regel 17: merk op dat de hoofdtekst van het antwoord van het type [string] is en dus klaar is om te worden verzonden (regel 30);
- regel 22: het antwoord zal UTF-8-tekens bevatten;
- regel 24: statuscode HTTP van het antwoord;
- regels 26-28: toevoeging van de headers HTTP die door de aanroepende code zijn opgegeven;
- regels 30-31: verzending van het antwoord naar de klant;
Tot slot nogmaals de code van de hoofdcontroller die vraagt om het verzenden van het antwoord naar de klant:
// de sleutels [action, état] worden aan het antwoord van de controller toegevoegd
$content = ["action" => $action, "état" => $état] + $content;
// het object [Response] wordt geïnstantieerd om het antwoord naar de client te verzenden
$response = __NAMESPACE__ . $config["types"][$type]["response"];
(new $response())->send($request, $session, $config, $statusCode, $content, $headers, $logger);
// het antwoord is verzonden – de bronnen worden vrijgegeven
$logger->close();
exit;
- regel 4: we stellen de naam van de te instantiëren klasse vast op [Response];
- regel 5: deze wordt geïnstantieerd en het antwoord wordt naar de client verzonden met behulp van de methode [send($request, $session, $config, $statusCode, $content, $headers, $logger)]. Omdat ze dezelfde interface [InterfaceResponse] implementeren, hebben de methoden [send] van de verschillende soorten antwoorden allemaal dezelfde signatuur;
23.12.2. De klasse [JsonResponse]
Deze is al besproken in de paragraaf ‘link’. We geven de code echter nogmaals weer om de homogeniteit van de drie antwoordklassen beter te benadrukken:
De klasse [JsonResponse] implementeert de interface [InterfaceResponse] als volgt:
<?php
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
class JsonResponse extends ParentResponse implements InterfaceResponse {
// Verzoek $request: verzoek wordt verwerkt
// Sessie $session: de sessie van de webapplicatie
// array $config: de configuratie van de applicatie
// int statusCode: de statuscode van het antwoord
// array $content: het antwoord van de server
// array $headers: de HTTP-headers die aan het antwoord moeten worden toegevoegd
// Logger $logger: de logger om logbestanden te schrijven
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void {
// voorbereiding van de Symfony-serializer
$serializer = new Serializer(
[
// nodig voor het serialiseren van objecten
new ObjectNormalizer()],
// encoder jSON
// voor de opties, voeg OU toe tussen de verschillende opties
[new JsonEncoder(new JsonEncode([JsonEncode::OPTIONS => JSON_UNESCAPED_UNICODE]))]
);
// serialisatie jSON
$json = $serializer->serialize($content, 'json');
// headers
$headers = array_merge($headers, ["content-type" => "application/json"]);
// antwoord verzenden
parent::sendResponse($statusCode, $json, $headers);
// logboek
if ($logger !== NULL) {
$logger->write("réponse=$json\n");
}
}
}
Opmerkingen
- regel 13: de klasse implementeert de interface [InterfaceResponse];
- regel 13: de klasse is een onderklasse van de klasse [ParentResponse]. Alle typen van [Response] zijn afgeleid van deze klasse. Het is deze bovenliggende klasse die het antwoord naar de client stuurt (regel 46). Omdat deze code gemeenschappelijk was voor alle typen van [Response], is deze ondergebracht in een bovenliggende klasse;
- regels 33-40: instantiëren van de serializer [Symfony] die het antwoord van de server [$content] omzet in een string jSON (regel 42);
- regels 34-36: de eerste parameter van de constructor van [Serializer] is een array. Hierin wordt een instantie van de klasse [ObjectNormalizer] geplaatst, die nodig is voor de serialisatie van objecten. Dit komt in deze toepassing voor bij een lijst met simulaties, waarbij elke simulatie een instantie is van de klasse [Simulation];
- regel 39: de tweede parameter van de constructor van [Serializer] is eveneens een array: hierin worden alle encoders geplaatst die bij een serialisatie worden gebruikt (XML, jSON, CSV…);
- regel 39: hier zal slechts één encoder staan, van het type [JsonEncoder]. De constructor zonder parameters zou voldoende zijn geweest. Hier hebben we een parameter [JsonEncode] doorgegeven aan de constructor, uitsluitend om de coderingsopties jSON door te geven;
- regel 39: de parameter van de constructor [JsonEncode] is een array met opties. Hier gebruiken we de optie [JSON_UNESCAPED_UNICODE] om aan te geven dat de tekens UTF-8 in de tekenreeks jSON in hun oorspronkelijke vorm moeten worden weergegeven en niet moeten worden ‘geëscape’;
- regel 42: de hoofdtekst van het antwoord HHTP wordt geserialiseerd tot jSON dankzij de voorgaande serializer;
- regel 44: de header HTTP wordt toegevoegd, die de client laat weten dat er jSON naar hem wordt verzonden;
- regel 46: de bovenliggende klasse wordt gevraagd het antwoord naar de client te sturen;
- regels 48-50: we loggen het antwoord jSON;
23.12.3. De klasse [XmlResponse]
De klasse [XmlResponse] implementeert de interface [InterfaceResponse] als volgt:
<?php
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
class XmlResponse extends ParentResponse implements InterfaceResponse {
// Verzoek $request: verzoek wordt verwerkt
// Sessie $session: de sessie van de webapplicatie
// array $config: de configuratie van de applicatie
// int statusCode: de statuscode van het antwoord
// array $content: het antwoord van de server
// array $headers: de headers HTTP die aan het antwoord moeten worden toegevoegd
// Logger $logger: de logger om logbestanden te schrijven
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void {
// voorbereiding van de Symfony-serializer
$serializer = new Serializer(
// nodig voor het serialiseren van objecten
[new ObjectNormalizer()],
[
// serialisatie XML
new XmlEncoder(
[
XmlEncoder::ROOT_NODE_NAME => 'root',
XmlEncoder::ENCODING => 'utf-8'
]
),
// serialisatie jSON
new JsonEncoder(new JsonEncode([JsonEncode::OPTIONS => JSON_UNESCAPED_UNICODE]))
]
);
// serialisatie XML
$xml = $serializer->serialize($content, 'xml');
// headers
$headers = array_merge($headers, ["content-type" => "application/xml"]);
// antwoord verzenden
parent::sendResponse($statusCode, $xml, $headers);
// log
if ($logger !== NULL) {
// log in jSON
$log = $serializer->serialize($content, 'json');
$logger->write("réponse=$log\n");
}
}
}
Opmerkingen
- regels 34-48: instantiëren van een Symfony-serializer. De constructor accepteert twee parameters van het type array;
- regel 36: de eerste array bevat een instantie van het type [ObjectNormalizer] die betrokken is bij de serialisatie van objecten;
- regels 37-47: de tweede array bevat de encoders die voor de serialisatie worden gebruikt. Met dezelfde serializer kunnen verschillende soorten serialisatie worden gerealiseerd;
- regels 38-44: de encoder XML;
- regel 41: de root van de gegenereerde XML-code wordt vastgelegd. Deze krijgt de vorm <root>[autres balises XML]</root>;
- regel 42: de codering maakt gebruik van de tekens UTF-8;
- regel 46: de encoder jSON. Deze wordt gebruikt voor het logboek van het antwoord in het bestand [logs.txt], dat is opgesteld in jSON;
- regel 50: de hoofdtekst van het naar de client verzonden antwoord wordt geserialiseerd in XML;
- regel 52: aan de als parameter ontvangen headers (regel 30) wordt de header HTTP toegevoegd, die de klant aangeeft dat er een document XML naar hem wordt verzonden;
- regel 54: daadwerkelijke verzending van het antwoord naar de klant door de bovenliggende klasse;
- regels 56-60: loggen van het antwoord in jSON;
23.12.4. Tests [Postman]
We hebben alle mogelijke fouttests al uitgevoerd in jSON. Er valt niets meer te doen in XML. We tonen twee voorbeelden van een XML-antwoord:

Hierboven:
- in [1-3], het verzoek om de sessie te starten XML;
- in [4-7], het antwoord XML van de server;
Vanaf nu zullen alle antwoorden van de server in XML zijn. We kunnen alle reeds gebruikte verzoeken in [Postman] ongewijzigd overnemen en voor elk daarvan krijgen we een antwoord in XML. Laten we bijvoorbeeld een succesvolle authenticatie uitvoeren:

Hierboven:
- in [1-3], een geldig authenticatieverzoek;
- in [4-7], het antwoord XML van de server;
23.12.5. Het antwoord [HtmlResponse]
Wanneer het sessietype [html] is, wordt een object van het type [HtmlResponse] geïnstantieerd om het antwoord naar de client te verzenden. Dit object stuurt een HTML-stream naar de client, die afhankelijk is van de statuscode die is geretourneerd door de secundaire controller die de actie heeft verwerkt. Deze koppeling [état=>vue] wordt als volgt in het configuratiebestand [config.json] vastgelegd:
"vues": {
"vue-authentification.php": [700, 221, 400],
"vue-calcul-impot.php": [200, 300, 341, 350, 800],
"vue-liste-simulations.php": [500, 600]
},
"vue-erreurs": "vue-erreurs.php"
Deze configuratie kan als volgt worden geïnterpreteerd: [‘nom de la vue’ => ‘états associés à cette vue’]
- regel 2: als de secundaire controller een status uit de tabel [700, 221, 400] heeft geretourneerd, dan moet de weergave [vue-authentification.php] worden weergegeven;
- regel 3: als de secundaire controller een status uit de tabel [200, 300, 341, 350, 800] heeft geretourneerd, moet de weergave [vue-calcul-impot.php] worden weergegeven;
- regel 4: als de secundaire controller een status uit de tabel [500, 600] heeft geretourneerd, moet de weergave [vue-liste-simulations.php] worden weergegeven;
- regel 6: als de secundaire controller een status heeft geretourneerd die in geen van de voorgaande tabellen voorkomt, moet de weergave [vue-erreurs.php] worden weergegeven;
De weergaven zijn verzameld in de map [Views] van het project:

De code van de klasse [HtmlResponse] is als volgt:
<?php
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
class HtmlResponse extends ParentResponse implements InterfaceResponse {
// Verzoek $request: verzoek wordt verwerkt
// Sessie $session: de sessie van de webapplicatie
// array $config: de configuratie van de applicatie
// int statusCode: de statuscode van het antwoord
// array $content: het antwoord van de server
// array $headers: de headers HTTP die aan het antwoord moeten worden toegevoegd
// Logger $logger: de logger om logbestanden te schrijven
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void {
// voorbereiding van de Symfony-serializer
$serializer = new Serializer(
[
// voor het serialiseren van objecten
new ObjectNormalizer()],
[
// voor de serialisatie jSON van het logboek van het antwoord
new JsonEncoder(new JsonEncode([JsonEncode::OPTIONS => JSON_UNESCAPED_UNICODE]))
]
);
// het antwoord HTML is afhankelijk van de statuscode die door de controller wordt geretourneerd
$état = $content["état"];
// aan elke status hoort een weergave – deze wordt opgezocht in de configuratie van de applicatie
// de lijst met weergaven
$vues = array_keys($config["vues"]);
$trouvé = false;
$i = 0;
// de lijst met weergaven wordt doorlopen
while (!$trouvé && $i < count($vues)) {
// statussen die bij weergave nr. i horen
$états = $config["vues"][$vues[$i]];
// bevindt het gezochte rapport zich onder de rapporten die aan weergave nr. i zijn gekoppeld?
if (in_array($état, $états)) {
// de weergegeven weergave is weergave nr. i
$vueRéponse = $vues[$i];
$trouvé = true;
}
// volgende weergave
$i++;
}
// Gevonden?
if (!$trouvé) {
// als er geen weergave bestaat voor de huidige status van de applicatie
// wordt de foutweergave weergegeven
$vueRéponse = $config["vue-erreurs"];
}
// de weer te geven weergave HTML wordt opgehaald in een tekenreeks
ob_start();
require __DIR__ . "/../Views/$vueRéponse";
$html = ob_get_clean();
// in de headers wordt aangegeven dat HTML wordt verzonden
$headers = array_merge($headers, ["content-type" => "text/html"]);
// de bovenliggende klasse zorgt voor het daadwerkelijke verzenden van het antwoord
parent::sendResponse($statusCode, $html, $headers);
// log in jSON van het antwoord zonder de HTML
if ($logger !== NULL) {
// log in jSON van het antwoord van de secundaire controller die de actie heeft verwerkt
$log = $serializer->serialize($content, 'json');
$logger->write("réponse=$log\n");
}
}
}
Opmerkingen
- regels 32-41: er wordt een Symfony-serializer geïnstantieerd. Deze is nodig voor het logboek jSON van het antwoord van de controller die de actie heeft verwerkt (regels 72-82);
- regels 42-57: in de configuratie van de applicatie wordt gezocht naar de weergave die moet worden getoond. Deze is afhankelijk van de statuscode die is geretourneerd door de controller die de actie heeft verwerkt. Deze code staat in [$content[‘état’]] (regel 43);
- regels 42-61: er wordt gezocht naar de weergave die bij deze status hoort;
- regels 62-67: als er geen weergave is gevonden, is er sprake van een abnormale statuscode voor de applicatie HTML. Dit begrip ‘abnormale statussen’ wordt verderop nader toegelicht. In dit geval wordt een foutmelding weergegeven;
- regels 68-70: de code PHP van de geselecteerde weergave wordt geïnterpreteerd en het resultaat wordt opgeslagen in de variabele [$html] (regel 71);
- deze code verdient enige uitleg. Stel dat de geselecteerde weergave [vue-authentification.php] is, die een webformulier voor authenticatie weergeeft:
- regel 69: de functie [ob_start] start wat in de documentatie een ‘uitvoervertraging’ wordt genoemd. Alles wat wordt geschreven door print- en require-bewerkingen… en wat normaal gesproken onmiddellijk naar de client wordt verzonden, gaat naar een uitvoerbuffer (ob = output buffer) zonder naar de client te worden verzonden;
- regel 70: de weergave [vue-authentification.php] wordt geladen; dit is een dynamische weergave van het type HTML die code van het type PHP bevat. Er gebeuren dan twee dingen:
- de code PHP van de weergave [vue-authentification.php] wordt geladen en geïnterpreteerd. Het resultaat is een weergave die we [vue-authentification.html] zullen noemen en die alleen code HTML, of zelfs CSS en JavaScript bevat, maar geen PHP meer;
- deze code HTML wordt normaal gesproken naar de client verzonden. Dit geldt in feite voor alle tekst die de interpreter PHP tegenkomt en die geen code PHP is. Vanwege de uitvoervertraging wordt deze code HTML in de uitvoerbuffer geplaatst zonder naar de klant te worden verzonden;
- regel 71: de functie [ob_get_clean] doet twee dingen:
- ze plaatst de inhoud van de uitvoerbuffer, dus de pagina [vue-authentification.html] die daarin is geplaatst, in de variabele [$html];
- ze leegt de uitvoerbuffer. Voor de uitvoerbuffer is het alsof er niets is gebeurd. Bovendien heeft de klant nog steeds niets ontvangen;
- regel 70: we bevinden ons hier tijdens de uitvoering van de klasse [HtmlResponse], die zich in de map [Responses] bevindt. Om de weergave te vinden, moet men dus één niveau omhoog gaan naar [..] en vervolgens naar de map [Views] gaan. [__DIR__] is de absolute naam van de map waarin het script zich bevindt dat momenteel wordt uitgevoerd; in ons voorbeeld is dat de map [C:/myprograms/laragon-lite/www/php7/scripts-web/impots/13/Responses];
- regel 73: aan de HTTP-headers die als parameter zijn ontvangen (regel 29), wordt de header toegevoegd die de client laat weten dat er HTML naar hem wordt verzonden;
- regel 75: de bovenliggende klasse wordt gevraagd om het antwoord daadwerkelijk naar de client te verzenden;
- regels 77-81: het antwoord [$content], geleverd door de secundaire controller die de lopende actie heeft verwerkt, wordt geregistreerd als jSON;
23.12.6. Tests [Postman]
Om de modus HTML van de sessie daadwerkelijk te testen, zouden we alle weergaven moeten doorlopen. Dat zullen we later doen. We gaan de volgende test uitvoeren:
Laten we de lijst met weergaven in het configuratiebestand bekijken:
"vues": {
"vue-authentification.php": [700, 221, 400],
"vue-calcul-impot.php": [200, 300, 341, 350, 800],
"vue-liste-simulations.php": [500, 600]
},
"vue-erreurs": "vue-erreurs.php"
We kunnen de context achter sommige van de bovenstaande statuscodes achterhalen door de uitgevoerde [Postman]-tests te bekijken:

We zien dat de statuscode [700] die is van een geslaagde actie [init-session] [2]. Hierboven zien we een antwoord jSON, maar het kan ook van het type XML of HTML zijn. Dit laatste geval zal worden getest. Volgens het configuratiebestand vormt de weergave [vue-authentification.php] het antwoord HTML. Laten we dit controleren.

Hierboven:
- in [1-3] wordt een sessie HTML geïnitialiseerd. We verwachten dus een antwoord HTML;
- in [4-8], het antwoord HTML van de server;
- via het tabblad [8] kan een voorbeeld van de ontvangen code HTML worden bekeken;

- in [8-9], een voorbeeld van de weergave HTML;
23.13. De webapplicatie HTML
23.13.1. Overzicht van de weergaven
De webapplicatie HTML maakt gebruik van vier weergaven:
De authenticatieweergave:

De belastingberekeningspagina:

De weergave met de lijst van simulaties:

Het overzicht van onverwachte fouten:

We zullen deze weergaven een voor een beschrijven.
23.13.2. Het authenticatieoverzicht
23.13.2.1. Overzicht van de weergave
De authenticatieweergave ziet er als volgt uit:

De weergave bestaat uit twee elementen die we fragmenten noemen:
- het fragment [1] wordt gegenereerd door een script [v-bandeau.php];
- het fragment [2] wordt gegenereerd door een script [v-authentification.php];
De authenticatiepagina wordt gegenereerd door de volgende pagina [vue-authentification.php]:
<?php
// testgegevens van de pagina
// de gegevens van de pagina worden ingekapseld in $page
…
?>
<!doctype html>
<html lang="fr">
<head>
<!-- Vereiste metatags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<title>Application impots</title>
</head>
<body>
<div class="container">
<!-- banner met 1 rij en 12 kolommen -->
<?php require "v-bandeau.php"; ?>
<!-- aanmeldingsformulier met 9 kolommen -->
<div class="row">
<div class="col-md-9">
<?php require "v-authentification.php" ?>
</div>
</div>
<?php
// bij een fout wordt een foutmelding weergegeven
if ($modèle->error) {
print <<<EOT
<div class="row">
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
Les erreurs suivantes se sont produites :
<ul>$modèle->erreurs</ul>
</div>
</div>
</div>
EOT;
}
?>
</div>
</body>
</html>
Opmerkingen
- regel 7: een document HTML begint met deze regel;
- regels 8-44: de pagina HTML is ingekapseld in de tags <html> </html>;
- regels 9-16: header (head) van het document HTML;
- regel 11: de tag <meta charset> geeft aan dat het document is gecodeerd in UTF-8;
- regel 12: de tag <meta name=’viewport’> stelt de initiële weergave van de view in: over de volledige breedte van het scherm waarop deze wordt weergegeven (width) op de oorspronkelijke grootte (initial-scale), zonder dat de afmetingen worden aangepast aan een kleiner scherm (shrink-to-fit);
- regel 14: de tag <link rel=’stylesheet’> verwijst naar het bestand CSS dat het uiterlijk van de weergave bepaalt. We gebruiken hier het framework CSS Bootstrap 4.1.3 [https://getbootstrap.com/docs/4.0/getting-started/introduction/] ;
- regel 15: de tag <title> bepaalt de titel van de pagina:

- regels 17-43: de hoofdtekst van de webpagina is ingekapseld in de tags <body></body>;
- regels 18-42: de tag <div> bakent een sectie van de weergegeven pagina af. De attributen [class] die in de weergave worden gebruikt, verwijzen allemaal naar het Bootstrap-framework CSS. De tag <div class=’container’> bakent een Bootstrap-container af;
- regel 20: het script [v-bandeau.php] wordt opgenomen. Dit script genereert de koptekst [1] van de pagina. We zullen dit binnenkort beschrijven;
- regels 22-26: de tag <div class=’row’> markeert een Bootstrap-rij. Deze rijen bestaan uit 12 kolommen;
- regel 23: de tag <div class=’col-md-9’> markeert een sectie van 9 kolommen;
- regel 24: we voegen het script [v-authentification.php] toe dat het authenticatieformulier [2] op de pagina weergeeft. We zullen dit binnenkort beschrijven;
- regel 27: de tag <?php voegt PHP-code toe aan de pagina HTML. Deze code wordt uitgevoerd voordat de pagina HTML wordt weergegeven en kan deze wijzigen;
- regel 29: alle dynamische gegevens van de weergegeven weergave worden ingekapseld in een object [$modèle] van het type [stdClass]. Dit is een willekeurige keuze. Men had in plaats daarvan ook een associatief array kunnen kiezen voor hetzelfde resultaat;
- regel 29: de authenticatie mislukt als de gebruiker onjuiste inloggegevens invoert. In dat geval wordt het authenticatiescherm opnieuw weergegeven met een foutmelding. Het attribuut [$modèle→error] geeft aan of deze foutmelding moet worden weergegeven;
- regels 30-39: deze syntaxis schrijft alle tekst weg die tussen de symbolen PHP <<<EOT (regel 30 – u kunt in plaats van EOT=End Of Text invullen wat u wilt) en het symbool EOT op regel 39 (moet identiek zijn aan het symbool dat op regel 30 wordt gebruikt). Het symbool moet in de eerste kolom van regel 39 worden geschreven. De variabelen PHP die zich in de tekst tussen de twee symbolen EOT bevinden, worden geïnterpreteerd;
- regels 33-36: bakenen een gebied met een roze achtergrond af (class="alert alert-danger") (regel 33);

- regel 34: een tekst;
- regel 35: de tag HTML <ul> (onbestorde lijst) geeft een lijst met opsommingstekens weer. Elk element van de lijst moet de syntaxis <li>element</li> hebben;
Laten we uit deze code de dynamische elementen onthouden die moeten worden gedefinieerd:
- [$modèle→error]: om een foutmelding weer te geven;
- [$modèle→erreurs]: een lijst (in de zin van HTML) met foutmeldingen;
23.13.2.2. Het fragment [v-bandeau.php]
Het fragment [v-bandeau.php] geeft de bovenste balk weer in alle weergaven van de webapplicatie:

De code van het fragment [v-bandeau.php] is als volgt:
<!-- Bootstrap Jumbotron -->
<div class="jumbotron">
<div class="row">
<div class="col-md-4">
<img src="<?= $logo ?>" alt="Cerisier en fleurs" />
</div>
<div class="col-md-8">
<h1>
Calculez votre impôt
</h1>
</div>
</div>
</div>
Opmerkingen
- regels 2-13: de banner is ingekapseld in een Bootstrap-sectie van het type Jumbotron [<div class="jumbotron">]. Deze Bootstrap-klasse geeft de weergegeven inhoud een specifieke stijl om deze te laten opvallen;
- regels 3-12: een Bootstrap-rij;
- regels 4-6: een afbeelding [img] is geplaatst in de eerste vier kolommen van de regel;
- regel 5: de syntaxis [<?= $logo ?>] is gelijk aan de syntaxis [<?php print $logo ?>]. Met andere woorden: de waarde van het attribuut [src] is gelijk aan de waarde van de variabele PHP [$logo];
- regels 7-11: de overige 8 kolommen van de regel (ter herinnering: er zijn er in totaal 12) worden gebruikt om tekst (regel 9) in grote letters (<h1>, regels 8-10) weer te geven;
Dynamische elementen:
- [$logo]: URL van de afbeelding die in de banner wordt weergegeven;
23.13.2.3. Het fragment [v-authentification.php]
Het fragment [v-authentification .php] geeft het authenticatieformulier van de webapplicatie weer:

De code van het fragment [v-authentification.php] is als volgt:
<!-- formulier HTML – de waarden worden verzonden met de actie [authentifier-utilisateur] -->
<form method="post" action="main.php?action=authentifier-utilisateur">
<!-- titel -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
<!-- Bootstrap-formulier -->
<fieldset class="form-group">
<!-- eerste regel -->
<div class="form-group row">
<!-- tekst -->
<label for="user" class="col-md-3 col-form-label">Nom d'utilisateur</label>
<div class="col-md-4">
<!-- tekstinvoerveld -->
<input type="text" class="form-control" id="user" name="user"
placeholder="Nom d'utilisateur" value="<?= $modèle->login ?>">
</div>
</div>
<!-- 2e regel -->
<div class="form-group row">
<!-- tekst -->
<label for="password" class="col-md-3 col-form-label">Mot de passe</label>
<!-- tekstinvoervak -->
<div class="col-md-4">
<input type="password" class="form-control" id="password" name="password"
placeholder="Mot de passe">
</div>
</div>
<!-- knop van het type [submit] op een derde regel-->
<div class="form-group row">
<div class="col-md-2">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</div>
</fieldset>
</form>
Opmerkingen
- regels 2-39: de tag <form> omkadert een formulier HTML. Dit formulier heeft doorgaans de volgende kenmerken:
- het definieert invoervelden (tags <input> in de regels 17 en 27);
- het heeft een knop van het type [submit] (regel 34) die de ingevoerde waarden verzendt naar het URL dat is opgegeven in het attribuut [action] van de tag [form] (regel 2). De methode HTTP die wordt gebruikt om deze URL op te roepen, wordt gespecificeerd in het attribuut [method] van de tag [form] (regel 2);
- wanneer de gebruiker hier op de knop [Valider] (regel 34) klikt, zal de browser de in het formulier ingevoerde waarden naar de URL [main.php?action=authentifier-utilisateur] (regel 2) verzenden (regel 2);
- de verzonden waarden zijn de waarden die de gebruiker heeft ingevoerd in de invoervelden van de regels 17 en 27. Ze worden verzonden in de vorm [user=xx&password=yy]. De namen van de parameters [user, password] zijn die van de attributen [name] van de invoervelden op de regels 17 en 27;
- regel 5-7: een Bootstrap-sectie om een titel op een blauwe achtergrond weer te geven:

- regels 10-37: een Bootstrap-formulier. Alle elementen van het formulier krijgen dan een bepaalde opmaak;
- regels 12-20: definiëren de eerste regel van het formulier:
![]()
- regel 14 definieert het label [1] over drie kolommen. Het attribuut [for] van de tag [label] koppelt de tekst aan het attribuut [id] van het invoerveld op regel 17;
- regels 15-19: plaatst het invoerveld in een groep van vier kolommen;
- regel 17: de tag HTML [input] beschrijft een invoerveld. Deze heeft verschillende parameters:
- [type=’text’]: dit is een tekstinvoerveld. Hierin kan alles worden ingevoerd;
- [class=’form-control’]: Bootstrap-stijl voor het invoerveld;
- [id=’user’]: de ID van het invoerveld. Deze ID wordt doorgaans gebruikt door CSS en de JavaScript-code;
- [name=’user’]: naam van het invoerveld. Onder deze naam wordt de door de gebruiker ingevoerde waarde door de browser verzonden;
- [placeholder=’invite’]: de tekst die in het invoerveld wordt weergegeven wanneer de gebruiker nog niets heeft ingevoerd;
![]()
- [value=’valeur’]: de tekst ‘waarde’ wordt in het invoerveld weergegeven zodra dit wordt getoond, dus voordat de gebruiker iets anders invoert. Dit mechanisme wordt gebruikt om bij een fout de invoer weer te geven die de fout heeft veroorzaakt. Hier is deze waarde de waarde van de variabele PHP [$modèle→login];
- regels 21-30: een soortgelijke code voor het invoeren van het wachtwoord;
- regel 27: [type=’password’] zorgt ervoor dat er een tekstinvoerveld is (je kunt alles invoeren), maar de ingevoerde tekens worden verborgen:
![]()
- regels 32-36: een derde regel voor de knop [Valider];
- regel 34: omdat deze knop het attribuut [type=submit] heeft, zorgt een klik op deze knop ervoor dat de browser de ingevoerde waarden naar de server verstuurt, zoals eerder is uitgelegd. Het attribuut CSS [class="btn btn-primary"] geeft een blauwe knop weer:

Er moet nog één ding worden uitgelegd. In regel 2 definieert het attribuut [action="main.php?action=authentifier-utilisateur"] een onvolledige URL (deze begint niet met http://machine:port/chemin). In ons voorbeeld hebben alle URL-elementen in de applicatie de vorm [http://localhost/php7/scripts-web/impots/version-12/main.php?action=xx]. Het authenticatieoverzicht wordt verkregen met verschillende URL-elementen:
- [http://localhost/php7/scripts-web/impots/version-12/main.php?action=init-session&type=html];
- [http://localhost/php7/scripts-web/impots/version-12/main.php?action=authentifier-utilisateur]
Deze URL verwijzen naar een document [main.php] in het pad [http://localhost/php7/scripts-web/impots/version-12]. Dit geldt voor alle URL van deze applicatie. De parameter [action="main.php?action=authentifier-utilisateur"] krijgt dit pad als voorvoegsel op het moment dat de ingevoerde waarden worden verzonden. Deze worden dus verzonden naar de URL [http://localhost/php7/scripts-web/impots/version-12/main.php?action=authentifier-utilisateur].
23.13.2.4. Visuele tests
De weergaven kunnen ruim voordat ze in de applicatie worden geïntegreerd, worden getest. Het gaat hier om het testen van hun visuele aspect. We verzamelen alle testweergaven in de map [Tests] van het project:

Om de weergave [vue-authentification.php] te testen, moeten we het gegevensmodel aanmaken dat deze weergave zal weergeven:
<?php
// testgegevens van de pagina
//
// het weergavemodel wordt berekend
$modèle = getModelForThisView();
function getModelForThisView(): object {
// de paginagegevens worden ingekapseld in $modèle
$modèle = new \stdClass();
// gebruikers-ID
$modèle->login = "albert";
// foutenlijst
$modèle->error = TRUE;
$erreurs = ["erreur1", "erreur2"];
// er wordt een lijst HTML met fouten samengesteld
$content = "";
foreach ($erreurs as $erreur) {
$content .= "<li>$erreur</li>";
}
$modèle->erreurs = $content;
// afbeelding van de banner
$modèle->logo = "http://localhost/php7/scripts-web/impots/version-12/Tests/logo.jpg";
// het sjabloon wordt weergegeven
return $modèle;
}
?>
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Vereiste metatags -->
…
</head>
<body>
….
</body>
</html>
Opmerkingen
- regels 1-5: de authenticatieweergave bevat dynamische onderdelen die worden aangestuurd door het object [$modèle]. Dit object noemen we het model van de weergave. Volgens een van de twee definities die voor de afkorting MVC worden gegeven, is dit de M van MVC;
- regel 5: het weergavemodel wordt berekend door de functie [getModelForThisView];
- regel 9: het weergavemodel wordt ingekapseld in een type [stdClass];
- regels 10-22: er worden testwaarden gedefinieerd voor de dynamische elementen van de authenticatieweergave;
De visuele test kan vanuit NetBeans worden uitgevoerd:

We gaan door met deze visuele tests totdat we tevreden zijn met het resultaat.
23.13.2.5. Berekening van het weergavemodel
Zodra het visuele aspect van de weergave is vastgesteld, kan het model van de weergave onder reële omstandigheden worden berekend. Laten we nog eens kijken naar de statuscodes die naar deze weergave leiden. Deze zijn te vinden in het configuratiebestand:
"vues": {
"vue-authentification.php": [700, 221, 400],
"vue-calcul-impot.php": [200, 300, 341, 350, 800],
"vue-liste-simulations.php": [500, 600]
},
"vue-erreurs": "vue-erreurs.php"
Het zijn dus de statuscodes [700, 221, 400] die ervoor zorgen dat het authenticatiescherm wordt weergegeven. Om de betekenis van deze codes te achterhalen, kunnen we gebruikmaken van de tests [Postman] die zijn uitgevoerd op de applicatie jSON:
- [init-session-json-700]: 700 is de statuscode na een geslaagde actie [init-session]: het authenticatieformulier wordt dan leeg weergegeven;
- [authentifier-utilisateur-221]: 221 is de statuscode na een mislukte actie [authentifier-utilisateur] (inloggegevens niet herkend): het authenticatieformulier wordt dan weergegeven zodat het kan worden gecorrigeerd;
- [fin-session-400]: 400 is de statuscode na een geslaagde actie [fin-session]: het authenticatieformulier wordt dan leeg weergegeven;
Nu we weten wanneer het authenticatieformulier moet worden weergegeven, kunnen we het sjabloon ervan berekenen in [vue-authentification.php]:

De code voor het berekenen van het sjabloon van de weergave [vue-authentification.php] is als volgt:
<?php
// de volgende variabelen worden overgenomen
// Verzoek $request: het huidige verzoek
// Sessie $session: de sessie van de applicatie
// array $config: de configuratie van de applicatie
// array $content: het antwoord van de controller
//
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
// het model van de weergave wordt berekend
$modèle = getModelForThisView($request, $session, $config, $content);
function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
// de gegevens van de pagina worden ingekapseld in $modèle
$modèle = new stdClass();
// status van de applicatie
$état = $content["état"];
// het model is afhankelijk van de status
switch ($état) {
case 700:
case 400:
// geval waarbij het lege formulier wordt weergegeven
$modèle->login = "";
// er is geen foutmelding
$modèle->error = FALSE;
break;
case 221:
// verificatie mislukt
// de oorspronkelijk ingevoerde gebruiker wordt opnieuw weergegeven
$modèle->login = $request->request->get("user");
// er is een fout te tonen
$modèle->error = TRUE;
// lijst HTML met foutmeldingen – hier slechts één
$modèle->erreurs = "<li>Echec de l'authentification</li>";
}
// resultaat
return $modèle;
}
?>
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
…
</head>
<body>
…
</body>
</html>
Opmerkingen
- regels 3-6: hier worden de variabelen opgeroepen die zijn overgenomen van de klasse [HtmlResponse], die ervoor zorgt dat de weergave [vue-authentification.php] wordt weergegeven door een [require];
- regels 9-10: de Symfony-klassen die in de code van de weergave worden gebruikt;
- regels 15-40: de functie [getModelForThisView] is verantwoordelijk voor het berekenen van het model van de weergave;
- regel 19: de statuscode wordt opgehaald die is geretourneerd door de controller die de huidige actie heeft verwerkt;
- regels 21-37: het sjabloon is afhankelijk van deze statuscode;
- regels 22-28: het geval waarin een leeg authenticatieformulier moet worden weergegeven;
- regels 29-37: in geval van een mislukte authenticatie: de ingevoerde gebruikersnaam wordt weergegeven en er verschijnt een foutmelding. De gebruiker kan dan via het toetsenbord een nieuwe authenticatiepoging doen;
Er is een specifiek sjabloon geschreven voor de banner [v-bandeau.php]:
<?php
// logo
$scheme = $request->server->get('REQUEST_SCHEME'); // http
$host = $request->server->get('SERVER_NAME'); // localhost
$port = $request->server->get('SERVER_PORT'); // 80
$uri = $request->server->get('REQUEST_URI'); // /php7/scripts-web/impots/version-12/main.php?action=xxx
$champs = [];
preg_match("/(.+)\/.+?$/", $uri, $champs);
$root = $champs[1]; // /php7/scripts-web/impots/version-12
$modèle->logo = "$scheme://$host:$port$root/Views/logo.jpg"; // http://localhost:80/php7/scripts-web/impots/version-12/Views/logo.jpg
?>
<!-- Bootstrap Jumbotron -->
<div class="jumbotron">
<div class="row">
<div class="col-md-4">
<img src="<?= $modèle->logo ?>" alt="Cerisier en fleurs" />
</div>
<div class="col-md-8">
<h1>
Calculez votre impôt
</h1>
</div>
</div>
</div>
Opmerkingen
- in regel 16 wordt de variabele [$modèle→logo] gebruikt, wat overeenkomt met de URL van het bannerlogo. In plaats van deze variabele vier keer te berekenen voor de vier weergaven van de applicatie, is deze berekening ondergebracht in het fragment [v-bandeau.php];
- de regels 1-11 laten zien hoe de URL en [http://localhost:80/php7/scripts-web/impots/version-12/Views/logo.jpg] worden samengesteld op basis van de informatie die in de serveromgeving [$request→server] wordt gevonden;
23.13.2.6. Tests [Postman]
We hebben al verzoeken gemaakt die de codes [700, 221, 400] genereren, waarmee het authenticatiescherm wordt weergegeven. Laten we ze nog eens op een rijtje zetten:
- [init-session-html-700]: 700 is de statuscode na een succesvolle actie [init-session]: het authenticatieformulier wordt dan leeg weergegeven;
- [authentifier-utilisateur-221]: 221 is de statuscode na een mislukte [authentifier-utilisateur]-actie (inloggegevens niet herkend): het authenticatieformulier wordt dan weergegeven zodat het kan worden gecorrigeerd;
- [fin-session-400]: 400 is de statuscode na een geslaagde actie [fin-session]: het authenticatieformulier wordt dan leeg weergegeven;
Je hoeft ze alleen maar opnieuw te gebruiken en te kijken of ze de authenticatiepagina correct weergeven. We laten hier slechts twee tests zien:
- [init-session-html-700]: start van een sessie HTML;

- [authentifier-utilisateur-221]: authenticatie van de gebruiker [x, x];

Hierboven:
- het verzoek had de tekenreeks [user=x&password=x] verzonden;
- in [4] wordt een foutmelding weergegeven;
- in [3] werd de verkeerde gebruiker opnieuw weergegeven;
23.13.2.7. Conclusie
We hebben de weergave [vue-authentification.php] kunnen testen zonder de andere weergaven te hebben geschreven. Dit was mogelijk omdat:
- alle controllers zijn geschreven;
- [Postman] stelt ons in staat om verzoeken naar de server te sturen zonder dat we de weergaven nodig hebben. Bij het schrijven van controllers moet men zich ervan bewust zijn dat iedereen dit kan doen. We moeten dus voorbereid zijn op verzoeken die door geen enkele weergave zouden worden ondersteund. Deze worden handmatig verwerkt in [Postman]. We mogen nooit zomaar aannemen dat ‘dit verzoek onmogelijk is’. We moeten het controleren;
23.13.3. De weergave voor de belastingberekening
23.13.3.1. Overzicht van de weergave
De weergave voor de belastingberekening ziet er als volgt uit:

De weergave bestaat uit drie delen:
- 1: de bovenste balk wordt gegenereerd door het fragment [v-bandeau.php] dat al is gepresenteerd;
- 2: het formulier voor de belastingberekening, gegenereerd door het fragment [v-calcul-impot.php];
- 3: een menu met twee links, gegenereerd door het fragment [v-menu.php];
De weergave voor de belastingberekening wordt gegenereerd door het volgende script [vue-calcul-impot.php]:

<?php
// de volgende variabelen worden overgenomen
// Verzoek $request: het huidige verzoek
// Sessie $session: de sessie van de applicatie
// array $config: de configuratie van de applicatie
// array $content: het antwoord van de controller die de actie heeft verwerkt
//
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
// het model van de weergave wordt berekend
$modèle = getModelForThisView($request, $session, $config, $content);
function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
// de gegevens van de pagina worden ingekapseld in $modèle
$modèle = new \stdClass();
…
// het model wordt weergegeven
return $modèle;
}
?>
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Vereiste metatags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<title>Application impots</title>
</head>
<body>
<div class="container">
<!-- banner -->
<?php require "v-bandeau.php"; ?>
<!-- tweekolomslay-out -->
<div class="row">
<!-- het menu -->
<div class="col-md-3">
<?php require "v-menu.php" ?>
</div>
<!-- het berekeningsformulier -->
<div class="col-md-9">
<?php require "v-calcul-impot.php" ?>
</div>
</div>
<!-- succesmelding -->
<?php
if ($modèle->success) {
// er wordt een succesmelding weergegeven
print <<<EOT1
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-9">
<div class="alert alert-success" role="alert">
$modèle->impôt</br>
$modèle->décôte</br>\n
$modèle->réduction</br>\n
$modèle->surcôte</br>\n
$modèle->taux</br>\n
</div>
</div>
</div>
EOT1;
}
?>
<?php
if ($modèle->error) {
// foutenlijst met 9 kolommen
print <<<EOT2
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
L'erreur suivante s'est produite :
<ul>$modèle->erreurs</ul>
</div>
</div>
</div>
EOT2;
}
?>
</div>
</body>
</html>
Opmerkingen
- we geven alleen commentaar op nieuwe elementen die we nog niet zijn tegengekomen;
- regel 37: opname van de bovenste balk van de weergave in de eerste Bootstrap-regel van de weergave;
- regels 41-43: opname van het menu dat drie kolommen van de tweede Bootstrap-regel van de weergave in beslag zal nemen;
- regels 45-47: het formulier voor de belastingberekening wordt opgenomen, dat negen kolommen van de tweede Bootstrap-regel van de weergave in beslag neemt;
- regels 51-69: als de belastingberekening slaagt ([$modèle→success=TRUE]), wordt het resultaat van de belastingberekening weergegeven in een groen kader (regels 59-65). Dit kader bevindt zich in de derde Bootstrap-rij van de weergave (regel 54) en beslaat negen kolommen (regel 58) rechts van drie lege kolommen (regels 55-57). Dit kader komt dus direct onder het formulier voor de belastingberekening te staan;
- regels 71-87: als de belastingberekening voor [$modèle→error=TRUE] mislukt, wordt er een foutmelding weergegeven in een roze kader (regels 80-83). Dit kader bevindt zich in de derde Bootstrap-regel van de weergave (regel 75) en beslaat negen kolommen (regel 79) rechts van drie lege kolommen (regels 76-78). Dit kader komt dus direct onder het formulier voor de belastingberekening te staan;
23.13.3.2. Het fragment [v-calcul-impot.php]
Het fragment [v-calcul-impot.php] geeft het authenticatieformulier van de webapplicatie weer:

De code van het fragment [v-calcul-impot.php] is als volgt:
<!-- formulier HTML verzonden -->
<form method="post" action="main.php?action=calculer-impot">
<!-- bericht met 12 kolommen op een blauwe achtergrond -->
<div class="col-md-12">
<div class="alert alert-primary" role="alert">
<h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
</div>
</div>
<!-- formuliervelden -->
<fieldset class="form-group">
<!-- eerste regel met 9 kolommen -->
<div class="row">
<!-- tekst over 4 kolommen -->
<legend class="col-form-label col-md-4 pt-0">Etes-vous marié(e) ou pacsé(e)?</legend>
<!-- keuzerondjes over 5 kolommen-->
<div class="col-md-5">
<div class="form-check">
<input class="form-check-input" type="radio" name="marié" id="gridRadios1" value="oui" <?= $modèle->checkedOui ?>>
<label class="form-check-label" for="gridRadios1">
Oui
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="marié" id="gridRadios2" value="non" <?= $modèle->checkedNon ?>>
<label class="form-check-label" for="gridRadios2">
Non
</label>
</div>
</div>
</div>
<!-- tweede rij met 9 kolommen -->
<div class="form-group row">
<!-- tekst over 4 kolommen -->
<label for="enfants" class="col-md-4 col-form-label">Nombre d'enfants à charge</label>
<!-- numeriek invoerveld voor het aantal kinderen in 5 kolommen -->
<div class="col-md-5">
<input type="number" min="0" step="1" class="form-control" id="enfants" name="enfants" placeholder="Nombre d'enfants à charge" value="<?= $modèle->enfants ?>">
</div>
</div>
<!-- derde regel met 9 kolommen -->
<div class="form-group row">
<!-- tekst over 4 kolommen -->
<label for="salaire" class="col-md-4 col-form-label">Salaire annuel</label>
<!-- veld voor het invoeren van het salaris, verdeeld over 5 kolommen -->
<div class="col-md-5">
<input type="number" min="0" step="1" class="form-control" id="salaire" name="salaire" placeholder="Salaire annuel" aria-describedby="salaireHelp" value="<?= $modèle->salaire ?>">
<small id="salaireHelp" class="form-text text-muted">Arrondissez à l'euro inférieur</small>
</div>
</div>
<!-- vierde regel, knop [submit] over 5 kolommen -->
<div class="form-group row">
<div class="col-md-5">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</div>
</fieldset>
</form>
Opmerkingen
- regel 2: het formulier HTML wordt verzonden (attribuut [method]) naar URL [main.php?action=calculer-impot] (attribuut [action]). De verzonden waarden zijn de waarden van de invoervelden:
- de waarde van de aangevinkte keuzeknop in de vorm:
- [marié=oui] als het keuzerondje [Oui] is aangevinkt (regels 16-22). [marié] is de waarde van het attribuut [name] van regel 18, [oui] de waarde van het attribuut [value] van regel 18;
- [marié=non] als het keuzerondje [Non] is aangevinkt (regels 23-28). [marié] is de waarde van het attribuut [name] van regel 24, [non] de waarde van het attribuut [value] van regel 24;
- de waarde van het numerieke invoerveld op regel 37 in de vorm [enfants=xx], waarbij [enfants] de waarde is van het attribuut [name] op regel 37, en [xx] de waarde die de gebruiker via het toetsenbord heeft ingevoerd;
- de waarde van het numerieke invoerveld op regel 46 in de vorm [salaire=xx], waarbij [salaire] de waarde is van het attribuut [name] op regel 46, en [xx] de waarde die de gebruiker via het toetsenbord heeft ingevoerd;
- de waarde van de aangevinkte keuzeknop in de vorm:
Uiteindelijk zal de verzonden waarde de vorm [marié=xx&enfants=yy&salaire=zz] hebben.
- De ingevoerde waarden worden verzonden wanneer de gebruiker op de knop van het type [submit] op regel 53 klikt;
- regels 16-30: de twee keuzerondjes:
![]()
De twee keuzerondjes maken deel uit van dezelfde groep keuzerondjes omdat ze hetzelfde attribuut [name] hebben (regels 18, 24). De browser zorgt ervoor dat binnen een groep keuzerondjes er op elk moment slechts één is aangevinkt. Als je dus op één ervan klikt, wordt het keuzerondje dat eerder was aangevinkt gedeactiveerd;
- het zijn keuzerondjes vanwege het attribuut [type="radio"] (regels 18, 24);
- bij het weergeven van het formulier (vóór het invoeren) moet één van de keuzerondjes geselecteerd zijn: hiervoor volstaat het om het attribuut [checked=’checked’] toe te voegen aan de betreffende <input type="radio">-tag. Dit wordt gerealiseerd met dynamische variabelen:
- [<?= $modèle->checkedOui ?>] op regel 18;
- [<?= $modèle->checkedNon ?>] op regel 24;
Deze variabelen maken deel uit van het weergavesjabloon.
- regel 37: een numeriek invoerveld [type="number"] met een minimumwaarde van 0 [min="0"]. In recente browsers betekent dit dat de gebruiker alleen een getal >=0 kan invoeren. In diezelfde recente browsers kan de invoer plaatsvinden via een schuifbalk die naar boven of naar beneden kan worden geschoven. Het attribuut [step="1"] op regel 37 geeft aan dat de schuifregelaar in stappen van 1 eenheid werkt. Dit heeft tot gevolg dat de schuifregelaar alleen gehele getallen aanneemt die oplopen van 0 tot n met een stap van 1. Voor handmatige invoer betekent dit dat getallen met een komma niet worden geaccepteerd;
![]()
- regel 37: bij bepaalde weergaven moet het invoerveld voor kinderen vooraf worden ingevuld met de laatste invoer die in dit veld is gedaan. Hiervoor wordt het attribuut [value] gebruikt, dat de weer te geven waarde in het invoerveld vastlegt. Deze waarde is dynamisch en wordt gegenereerd door de variabele [$modèle→enfants];
- regel 46: dezelfde uitleg voor de invoer van het salaris als voor die van de kinderen;
- regel 53: de knop van het type [submit] die de POST activeert met de ingevoerde waarden in de URL en [main.php?action=calculer-impot];

23.13.3.3. Het fragment [v-menu.php]
Dit fragment toont een menu links van het formulier voor de belastingberekening:

De code van dit fragment is als volgt:
<!-- Bootstrap-menu -->
<nav class="nav flex-column">
<?php
// weergave van een lijst met links HTML
foreach($modèle->optionsMenu as $texte=>$url){
print <<<EOT3
<a class="nav-link" href="$url">$texte</a>
EOT3;
}
?>
</nav>
Opmerkingen
- regels 2-11: de tag HTML [nav] omkadert een deel van het document HTML met navigatielinks naar andere documenten;
- regel 7: de tag HTML [a] introduceert een navigatielink:
- [$url]: is de URL waarnaar wordt genavigeerd wanneer op de link [$texte] wordt geklikt. Dit is dan een bewerking [GET $url] die door de browser wordt uitgevoerd. Als [$url] een relatieve URL is, wordt er de root van de URL aan voorgezet die op dat moment in de adresbalk van de browser wordt weergegeven. Om dus de link [1] te verkrijgen, terwijl de huidige URL van de browser van het type [http://chemin/main.php?paramètres] is, wordt de volgende link aangemaakt:
- regel 5: het sjabloon [$modèle→optionsMenu] van het fragment zal een tabel zijn in de vorm:
[‘ Liste des simulations’=>’main.php?action=liste-simulations’,
‘ Fin de session’=>’main.php?action=fin-session’]
- regels 2, 7: de klassen CSS en [nav, flex-column, nav-link] zijn Bootstrap-klassen die het uiterlijk van het menu bepalen;
23.13.3.4. Visuele test
We verzamelen deze verschillende elementen in de map [Tests] en maken een testsjabloon voor de weergave [vue-calcul-impot.php]:

Het gegevensmodel van de weergave [vue-calcul-impot] ziet er als volgt uit:
<?php
// testgegevens van de pagina
//
// het view-sjabloon wordt berekend
$modèle = getModelForThisView();
function getModelForThisView(): object {
// de gegevens van de pagina worden ingekapseld in $modèle
$modèle = new \stdClass();
// formulier
$modèle->checkedOui = "";
$modèle->checkedNon = 'checked="checked"';
$modèle->enfants = 2;
$modèle->salaire = 300000;
// succesbericht
$modèle->success = TRUE;
$modèle->impôt = "Montant de l'impôt : 1000 euros";
$modèle->décôte = "Décôte : 15 euros";
$modèle->réduction = "Réduction : 20 euros";
$modèle->surcôte = "Surcôte : 0 euros";
$modèle->taux = "Taux d'imposition : 14 %";
// foutmelding
$modèle->error = TRUE;
$erreurs = ["erreur1", "erreur2"];
// er wordt een lijst HTML met fouten samengesteld
$content = "";
foreach ($erreurs as $erreur) {
$content .= "<li>$erreur</li>";
}
$modèle->erreurs = $content;
// menu
$modèle->optionsMenu = [
'Lijst met simulaties' => 'main.php?action=lijst-simulaties',
''Sessie beëindigen' => 'main.php?action=sessie-beëindigen'];
// bannerafbeelding
$modèle->logo = "http://localhost/php7/scripts-web/impots/version-12/Tests/logo.jpg";
// het sjabloon weergeven
return $modèle;
}
?>
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
…
</head>
<body>
…
</body>
</html>
Opmerkingen
- regels 7-39: we initialiseren alle dynamische delen van de weergave [vue-calcul-impot.php] en de fragmenten [v-calcul-impot.php] en [v-menu.php];
We testen de weergave [vue-calcul-impot.php]:

We krijgen het volgende resultaat:

We werken aan deze weergave totdat het visuele resultaat ons bevalt. Vervolgens kunnen we overgaan tot de integratie van de weergave in de webapplicatie die we aan het schrijven zijn.
23.13.3.5. Berekening van het weergavemodel

Zodra het visuele aspect van de weergave is vastgesteld, kunnen we overgaan tot het berekenen van het model van de weergave onder reële omstandigheden. Laten we nog eens kijken naar de statuscodes die naar deze weergave leiden. Deze zijn te vinden in het configuratiebestand:
"vues": {
"vue-authentification.php": [700, 221, 400],
"vue-calcul-impot.php": [200, 300, 341, 350, 800],
"vue-liste-simulations.php": [500, 600]
},
"vue-erreurs": "vue-erreurs.php"
Het zijn dus de statuscodes [200, 300, 341, 350, 800] die ervoor zorgen dat het authenticatiescherm wordt weergegeven. Om de betekenis van deze codes te achterhalen, kunnen we gebruikmaken van de tests [Postman] die zijn uitgevoerd op de applicatie jSON:
- [authentifier-utilisateur-200]: 200 is de statuscode na een geslaagde actie [authentifier-itilisateur]: het lege formulier voor de belastingberekening wordt dan weergegeven;
- [calculer-impot-300]: 300 is de statuscode na een succesvolle uitvoering van de actie [calculer-impot]. Vervolgens wordt het berekeningsformulier weergegeven met de ingevoerde gegevens en het belastingbedrag. De gebruiker kan dan een nieuwe berekening uitvoeren;
- [fin-session-400]: 400 is de statuscode na een succesvolle uitvoering van actie [fin-session]: het authenticatieformulier wordt dan leeg weergegeven;
- de statuscode [341] wordt verkregen bij een geldige belastingberekening, maar het ontbreken van een verbinding met SGBD leidt tot een fout;
- de statuscode [350] wordt verkregen bij een geldige belastingberekening, maar het ontbreken van een verbinding met de server [Redis] leidt tot een fout;
- de statuscode [800] zal later worden toegelicht. We zijn deze nog niet tegengekomen;
- hier is ervan uitgegaan dat de gebruiker een recente browser gebruikt. Met het onderzochte formulier is het dus niet mogelijk om negatieve getallen, niet-numerieke tekenreeksen of getallen met een komma in de invoervelden [enfants, salaire] in te voeren. Met oudere browsers zou dit wel mogelijk zijn. We zullen deze fouten behandelen als onverwachte fouten en vervolgens de weergave [vue-erreurs] weergeven;
Nu we weten wanneer het formulier voor de belastingberekening moet worden weergegeven, kunnen we het sjabloon ervan berekenen in [vue-calcul-impot.php]:
<?php
// de volgende variabelen worden overgenomen
// Verzoek $request: het huidige verzoek
// Sessie $session: de sessie van de applicatie
// array $config: de configuratie van de applicatie
// array $content: het antwoord van de controller die de actie heeft verwerkt
//
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
// het model van de weergave wordt berekend
$modèle = getModelForThisView($request, $session, $config, $content);
function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
// de gegevens van de pagina worden ingekapseld in $modèle
$modèle = new \stdClass();
// status van de applicatie
$état = $content["état"];
// het model is afhankelijk van de status
switch ($état) {
case 200 :
case 800:
// eerste weergave van een leeg formulier
$modèle->success = FALSE; $modèle->errror = FALSE;
$modèle->checkedNon = 'checked="checked"';
$modèle->checkedOui = "";
$modèle->enfants = "";
$modèle->salaire = "";
break;
case 300:
// berekening geslaagd – weergave van het resultaat
$modèle->success = TRUE;
$modèle->error = FALSE;
$modèle->impôt = "Montant de l'impôt : {$content["réponse"]["impôt"]} euros";
$modèle->décôte = "Décôte : {$content["réponse"]["décôte"]} euros";
$modèle->réduction = "Réduction : {$content["réponse"]["réduction"]} euros";
$modèle->surcôte = "Surcôte : {$content["réponse"]["surcôte"]} euros";
$modèle->taux = "Taux d'imposition : " . ($content["réponse"]["taux"] * 100) . " %";
// formulier hersteld met de ingevoerde waarden
$modèle->checkedOui = $request->request->get("marié") === "oui" ? 'checked="checked"' : "";
$modèle->checkedNon = $request->request->get("marié") === "oui" ? "" : 'checked="checked"';
$modèle->enfants = $request->request->get("enfants");
$modèle->salaire = $request->request->get("salaire");
break;
case 341:
// database HS
case 350:
// Redis-server HS
// formulier hersteld met de ingevoerde waarden
$modèle->checkedOui = $request->request->get("marié") === "oui" ? 'checked="checked"' : "";
$modèle->checkedNon = $request->request->get("marié") === "oui" ? "" : 'checked="checked"';
$modèle->enfants = $request->request->get("enfants");
$modèle->salaire = $request->request->get("salaire");
// fout
$modèle->success = FALSE;
$modèle->error = TRUE;
$modèle->erreurs = "<li>{$content["réponse"]}</li>";
break;
}
//menu
$modèle->optionsMenu = [
"Liste des simulations" => "main.php?action=lister-simulations",
"Fin de session" => "main.php?action=fin-session"];
// het sjabloon wordt weergegeven
return $modèle;
}
?>
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
…
<title>Application impots</title>
</head>
<body>
…
</body>
</html>
Opmerkingen
- regels 22-30: weergave van een leeg formulier;
- regels 31-45: geval waarin de belastingberekening is geslaagd. De ingevoerde waarden en het belastingbedrag worden opnieuw weergegeven;
- regels 46-59: geval waarin de belastingberekening mislukt vanwege de onbeschikbaarheid van een van de servers [Redis] of [MySQL];
- regels 62-64: berekening van de twee menuopties;
23.13.3.6. Tests [Postman]
Met de test [calculer-impot-300] krijgen we de statuscode 300. Dit betekent dat de belastingberekening is geslaagd:

- in [3], de waarden die tot het resultaat [2] hebben geleid;
Laten we eens een foutgeval bekijken: de fout [350] als gevolg van de onbeschikbaarheid van de server [Redis]:

23.13.4. Het overzicht van de simulatielijst
23.13.4.1. Overzicht van de weergave
De weergave met de lijst van simulaties ziet er als volgt uit:

De weergave die door het script [vue-liste-simulations] wordt gegenereerd, bestaat uit drie delen:
- 1: de bovenste balk wordt gegenereerd door het fragment [v-bandeau.php] dat al is getoond;
- 2: de tabel met simulaties, gegenereerd door het fragment [v-liste-simulations.php];
- 3: een menu met twee links, gegenereerd door het fragment [v-menu.php];
De weergave van de simulaties wordt gegenereerd door het volgende script [vue-liste-simulations.php]:

<?php
// het sjabloon van de weergave wordt berekend
$modèle = getModelForThisView();
function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
// de gegevens van de pagina worden ingekapseld in $modèle
$modèle = new \stdClass();
…
// het sjabloon wordt weergegeven
return $modèle;
}
?>
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Vereiste metatags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<title>Application impots</title>
</head>
<body>
<div class="container">
<!-- banner -->
<?php require "v-bandeau.php"; ?>
<!-- tweekolomslay-out -->
<div class="row">
<!-- menu met drie kolommen-->
<div class="col-md-3">
<?php require "v-menu.php" ?>
</div>
<!-- lijst met simulaties in 9 kolommen-->
<div class="col-md-9">
<?php require "v-liste-simulations.php" ?>
</div>
</div>
</div>
</body>
</html>
Opmerkingen
- regel 28: opname van de koptekst van de applicatie [1];
- regel 33: toevoeging van het menu [2]. Dit wordt in drie kolommen onder de banner weergegeven;
- regel 37: opname van de simulatietabel [3]. Deze wordt weergegeven in negen kolommen onder de banner en rechts van het menu;
We hebben al twee van de drie fragmenten van dit scherm besproken:
Het fragment [v-liste-simulations.php] luidt als volgt:
<!-- bericht op blauwe achtergrond -->
<div class="alert alert-primary" role="alert">
<h4>Liste de vos simulations</h4>
</div>
<!-- tabel met simulaties -->
<table class="table table-sm table-hover table-striped">
<!-- kopteksten van de zes kolommen van de tabel -->
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Marié</th>
<th scope="col">Nombre d'enfants</th>
<th scope="col">Salaire annuel</th>
<th scope="col">Montant impôt</th>
<th scope="col">Surcôte</th>
<th scope="col">Décôte</th>
<th scope="col">Réduction</th>
<th scope="col">Taux</th>
<th scope="col"></th>
</tr>
</thead>
<!-- hoofdgedeelte van de tabel (weergegeven gegevens) -->
<tbody>
<?php
$i = 0;
// elke simulatie wordt weergegeven door de simulatietabel te doorlopen
foreach ($modèle->simulations as $simulation) {
// weergave van een rij van de tabel met 6 kolommen - tag <tr>
// kolom 1: rijkop (simulatienummer) - tag <th scope='row'>
// kolom 2: parameterwaarde [marié] - tag <td>
// kolom 3: parameterwaarde [enfants] - tag <td>
// kolom 4: parameterwaarde [salaire] - tag <td>
// kolom 5: parameterwaarde [impôt] (van de belasting) - tag <td>
// kolom 6: parameterwaarde [surcôte] - tag <td>
// kolom 7: parameterwaarde [décôte] - tag <td>
// kolom 8: parameterwaarde [réduction] - tag <td>
// kolom 9: parameterwaarde [taux] (van de belasting) - tag <td>
// kolom 10: link om de simulatie te verwijderen - tag <td>
print <<<EOT
<tr>
<th scope="row">$i</th>
<td>{$simulation["marié"]}</td>
<td>{$simulation["enfants"]}</td>
<td>{$simulation["salaire"]}</td>
<td>{$simulation["impôt"]}</td>
<td>{$simulation["surcôte"]}</td>
<td>{$simulation["décôte"]}</td>
<td>{$simulation["réduction"]}</td>
<td>{$simulation["taux"]}</td>
<td><a href="main.php?action=supprimer-simulation&numéro=$i">Supprimer</a></td>
</tr>
EOT;
$i++;
}
?>
</tr>
</tbody>
</table>
Opmerkingen
- een tabel HTML is gemaakt met de tag <table> (regels 6 en 58);
- de kolomkoppen van de tabel worden binnen een <thead>-tag (table head, regels 8, 21) weergegeven. De <tr>-tag (table row, regels 9 en 20) bakent een rij af. In de regels 10-15 definieert de <th>-tag (table header) een kolomkop. Er zijn er dus tien. [scope="col"] geeft aan dat de koptekst van toepassing is op de kolom. [scope="row"] geeft aan dat de koptekst van toepassing is op de rij;
- regels 23-57: de tag <tbody> omkadert de gegevens die door de tabel worden weergegeven;
- regels 40-51: de tag <tr> omkadert een rij van de tabel;
- regel 41: de tag <th scope=’row’> definieert de koptekst van de rij;
- regels 42-50: elke tag <td> definieert een kolom van de rij;
- regel 27: de lijst met simulaties is te vinden in het model [$modèle→simulations], een associatieve tabel;
- regel 50: een link om de simulatie te verwijderen. Het model URL neemt het nummer over dat in de eerste kolom van de tabel wordt weergegeven (regel 41);
23.13.4.2. Visuele test
We brengen deze verschillende elementen samen in de map [Tests] en maken een testmodel voor de weergave [vue-liste-simulations.php]:

Het gegevensmodel van de weergave [vue-liste-simulations] ziet er als volgt uit:
<?php
// het weergavemodel wordt berekend
$modèle = getModelForThisView();
function getModelForThisView(): object {
// de gegevens van de pagina worden ingekapseld in $modèle
$modèle = new \stdClass();
// de simulaties worden in het door de pagina verwachte formaat geplaatst
$modèle->simulations = [
[
"marié" => "oui",
"enfants" => 2,
"salaire" => 60000,
"impôt" => 448,
"décôte" => 100,
"réduction" => 20,
"surcôte" => 0,
"taux" => 0.14
],
[
"marié" => "non",
"enfants" => 2,
"salaire" => 200000,
"impôt" => 25600,
"décôte" => 0,
"réduction" => 0,
"surcôte" => 8400,
"taux" => 0.45
]
];
// de menuopties
$modèle->optionsMenu = [
"Calcul de l'impôt" => "main.php?action=afficher-calcul-impot",
"Fin de session" => "main.php?action=fin-session"];
// afbeelding van de banner
$modèle->logo = "http://localhost/php7/scripts-web/impots/version-12/Tests/logo.jpg";
// we genereren het sjabloon
return $modèle;
}
?>
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
…
</head>
<body>
…
</body>
</html>
Opmerkingen
- regels 9-30: de tabel met simulaties die door de tabel HTML wordt weergegeven;
- regels 32-34: de tabel met menuopties;
Laten we deze weergave weergeven:

We krijgen het volgende resultaat:

We werken aan deze weergave totdat we tevreden zijn met het visuele resultaat. Vervolgens kunnen we de weergave integreren in de webapplicatie die we aan het schrijven zijn.
23.13.4.3. Berekening van het weergavemodel

Zodra het visuele aspect van de weergave is vastgesteld, kunnen we overgaan tot het berekenen van het model van de weergave onder reële omstandigheden. Laten we nog eens kijken naar de statuscodes die naar deze weergave leiden. Deze zijn te vinden in het configuratiebestand:
"vues": {
"vue-authentification.php": [700, 221, 400],
"vue-calcul-impot.php": [200, 300, 341, 350, 800],
"vue-liste-simulations.php": [500, 600]
},
"vue-erreurs": "vue-erreurs.php"
Het zijn dus de statuscodes [500, 600] die ervoor zorgen dat de weergave van de simulaties wordt getoond. Om de betekenis van deze codes te achterhalen, kunnen we gebruikmaken van de tests [Postman] die zijn uitgevoerd op de applicatie jSON:
- [lister-simulations-500]: 500 is de statuscode na een geslaagde actie [lister-simulations]: vervolgens wordt de lijst met door de gebruiker uitgevoerde simulaties weergegeven;
- [supprimer-simulation-600]: 600 is de statuscode na een succesvolle uitvoering van de actie [supprimer-simulation]. Vervolgens wordt de nieuwe lijst met simulaties weergegeven die na deze verwijdering is verkregen;
Nu we weten op welke momenten de lijst met simulaties moet worden weergegeven, kunnen we het model ervan berekenen in [vue-liste-simulations.php]:
<?php
// de volgende variabelen worden overgenomen
// Verzoek $request: het huidige verzoek
// Sessie $session: de sessie van de applicatie
// array $config: de configuratie van de applicatie
// array $content: het antwoord van de controller
// geen fouten mogelijk
// array $content: het antwoord van de controller
//
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
// het model van de weergave wordt berekend
$modèle = getModelForThisView($request, $session, $config, $content);
function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
// de gegevens van de pagina worden ingekapseld in $modèle
$modèle = new \stdClass();
// de simulaties worden in het door de pagina verwachte formaat gezet
// deze zijn te vinden in het antwoord van de controller die de actie heeft uitgevoerd
// in de vorm van een array van objecten van het type [Simulation]
$objetsSimulation = $content["réponse"];
// elk object van het type [Simulation] wordt omgezet in een associatieve array
$modèle->simulations = [];
foreach ($objetsSimulation as $objetSimulation) {
$modèle->simulations[] = [
"marié" => $objetSimulation->getMarié(),
"enfants" => $objetSimulation->getEnfants(),
"salaire" => $objetSimulation->getSalaire(),
"impôt" => $objetSimulation->getImpôt(),
"surcôte" => $objetSimulation->getSurcôte(),
"décôte" => $objetSimulation->getdécôte(),
"réduction" => $objetSimulation->getRéduction(),
"taux" => $objetSimulation->getTaux()
];
}
// de menuopties
$modèle->optionsMenu = [
"Calcul de l'impôt" => "main.php?action=afficher-calcul-impot",
"Fin de session" => "main.php?action=fin-session"];
// we maken het sjabloon
return $modèle;
}
?>
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
…
</head>
<body>
…
</body>
</html>
Opmerkingen
- regels 26-36: berekening van het model [$modèle→simulations] dat wordt gebruikt door het fragment [v-liste-simulations.php];
- regels 39-41: berekening van het model [$modèle→optionsMenu] dat wordt gebruikt door het fragment [v-menu.php];
23.13.4.4. Tests [Postman]
De test [lister-simulations-500] levert de statuscode 500 op. Deze komt overeen met een verzoek om de simulaties te bekijken:

Met de test [supprimer-simulation-600] krijgen we de statuscode 600. Dit komt overeen met het succesvol verwijderen van simulatie nr. 0. Het geretourneerde resultaat is een lijst met simulaties waarin één simulatie ontbreekt:

23.13.5. Het overzicht van onverwachte fouten
Onder een onverwachte fout verstaan we hier een fout die bij normaal gebruik van de webapplicatie niet had mogen optreden.
Laten we als voorbeeld de test [Postman] [calculer-impot-3xx] nemen, die als volgt is gedefinieerd:

- in [1-3], een verzoek POST met de actie [calculer-impot];
- in [4-6]: hier kan men naar eigen keuze de drie parameters van POST definiëren:
- [4]: de parameter [marié] ontbreekt;
- [5-6]: de parameters [enfants, salaire] zijn aanwezig maar ongeldig;
- in [9] worden deze drie fouten gemeld met statuscode 338;
In het formulier HTML van de webapplicatie kan dit echter niet voorkomen:
- alle parameters zijn aanwezig;
- de parameter [marié], die zijn waarde ontleent aan de attributen [value] van twee keuzerondjes, heeft noodzakelijkerwijs een van de waarden [oui] of [non];
- met een recente browser zorgen de attributen <input type=’number’ min=’0’ step=’1’ …> ervoor dat de invoer voor het aantal kinderen en het salaris noodzakelijkerwijs gehele getallen >=0 zijn;
Niets weerhoudt een gebruiker er echter van om [Postman] in te voeren en de bovenstaande test [calcul-impot-3xx] naar onze server te verzenden. We hebben gezien dat onze webapplicatie correct op dit verzoek kon reageren. We noemen een ‘onverwachte fout’ een fout die zich in het kader van de applicatie HTML niet zou mogen voordoen. Als deze zich voordoet, is dat waarschijnlijk omdat iemand probeert de applicatie te ‘hacken’. Omwille van de duidelijkheid hebben we besloten om in dergelijke gevallen een foutpagina weer te geven. In de praktijk zouden we de laatste pagina die naar de klant is verzonden opnieuw kunnen weergeven. Hiervoor volstaat het om het laatste verzonden antwoord HTML in de sessie op te slaan. Bij een onverwachte fout sturen we dit antwoord terug. Zo krijgt de gebruiker de indruk dat de server niet op zijn fouten reageert, aangezien de weergegeven pagina niet verandert.
23.13.5.1. Weergave van de pagina
De weergave die onverwachte fouten toont, ziet er als volgt uit:

De weergave die door het script [vue-erreurs.php] wordt gegenereerd, bestaat uit drie delen:
- 1: de bovenste balk wordt gegenereerd door het reeds gepresenteerde fragment [v-bandeau.php];
- 2: de onverwachte fout(en);
- 3: een menu met drie links, gegenereerd door het fragment [v-menu.php];
De weergave van de onverwachte fouten wordt gegenereerd door het volgende script [vue-erreurs.php]:

<?php
// het weergavemodel wordt berekend
$modèle = getModelForThisView();
function getModelForThisView(): object {
// de gegevens van de pagina worden ingekapseld in $modèle
$modèle = new \stdClass();
…
// het model wordt teruggestuurd
return $modèle;
}
?>
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
<!-- Vereiste metatags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<title>Application impots</title>
</head>
<body>
<div class="container">
<!-- banner met 12 kolommen -->
<?php require "v-bandeau.php"; ?>
<!-- rij met twee kolommen -->
<div class="row">
<!-- menu met 3 kolommen-->
<div class="col-md-3">
<?php require "v-menu.php" ?>
</div>
<!-- foutenlijst -->
<div class="col-md-9">
<?php
print <<<EOT
<div class="alert alert-danger" role="alert">
Les erreurs inattendues suivantes se sont produites :
<ul>$modèle->erreurs</ul>
</div>
EOT;
?>
</div>
</div>
</div>
</body>
</html>
Opmerkingen
- regel 27: opname van de banner van de applicatie [1];
- regel 32: opname van het menu [2]. Dit wordt in drie kolommen onder de banner weergegeven;
- regels 34-44: weergave van het foutgebied in negen kolommen;
- regels 37-44: de bewerking [print] die onverwachte fouten weergeeft;
- regel 38: deze weergave vindt plaats in een Bootstrap-kader met een roze achtergrond;
- regel 39: een inleidende tekst;
- regel 40: de tag <ul> omvat een lijst met opsommingstekens. Deze lijst met opsommingstekens wordt geleverd door het sjabloon [$modèle->erreurs];
We hebben de twee fragmenten van deze weergave al besproken:
23.13.5.2. Visuele test
We brengen deze verschillende elementen samen in de map [Tests] en maken een testmodel voor de weergave [vue-erreurs.php]:

Het gegevensmodel van de weergave [vue-erreurs.php] ziet er als volgt uit:
<?php
// het weergavemodel wordt berekend
$modèle = getModelForThisView();
function getModelForThisView(): object {
// de gegevens van de pagina worden ingekapseld in $modèle
$modèle = new \stdClass();
// de tabel met onverwachte fouten
$erreurs = ["erreur1", "erreur2"];
// de lijst met fouten wordt samengesteld HTML
$modèle->erreurs = "";
foreach ($erreurs as $erreur) {
$modèle->erreurs .= "<li>$erreur</li>";
}
// menuopties
$modèle->optionsMenu = [
"Calcul de l'impôt" => "main.php?action=afficher-calcul-impot",
"Liste des simulations" => "main.php?action=lister-simulations",
"Fin de session" => "main.php?action=fin-session",];
// afbeelding van de banner
$modèle->logo = "http://localhost/php7/scripts-web/impots/version-12/Tests/logo.jpg";
// het sjabloon wordt teruggestuurd
return $modèle;
}
?>
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
…
</head>
<body>
…
</body>
</html>
Opmerkingen
- regels 9-15: opbouw van de foutenlijst HTML;
- regels 17-20: de tabel met menuopties;
Laten we deze weergave weergeven:

We krijgen het volgende resultaat:

We werken aan deze weergave totdat we tevreden zijn met het visuele resultaat. Vervolgens kunnen we de weergave integreren in de webapplicatie die we aan het schrijven zijn.
23.13.5.3. Berekening van het weergavemodel

Zodra het visuele aspect van de weergave is vastgesteld, kunnen we overgaan tot het berekenen van het model van de weergave onder reële omstandigheden. Laten we nog eens kijken naar de statuscodes die naar deze weergave leiden. Deze zijn te vinden in het configuratiebestand:
"vues": {
"vue-authentification.php": [700, 221, 400],
"vue-calcul-impot.php": [200, 300, 341, 350, 800],
"vue-liste-simulations.php": [500, 600]
},
"vue-erreurs": "vue-erreurs.php"
Het zijn dus de statuscodes die niet voorkomen in de regels [2-4] die ervoor zorgen dat de weergave van onverwachte fouten wordt getoond.
De berekeningscode voor het weergavemodel [vue-erreurs.php] is als volgt:
<?php
// de volgende variabelen worden overgenomen
// Verzoek $request: het huidige verzoek
// Sessie $session: de sessie van de applicatie
// array $config: de configuratie van de applicatie
// array $content: het antwoord van de controller
//
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
// het model van de weergave wordt berekend
$modèle = getModelForThisView($request, $session, $config, $content);
function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
// de gegevens van de pagina worden ingekapseld in $modèle
$modèle = new \stdClass();
// de fouten uit het antwoord van de controller ophalen
$réponse = $content["réponse"];
if (!is_array($réponse)) {
// één foutmelding
$erreurs = [$réponse];
} else {
// meerdere foutmeldingen
$erreurs = $réponse;
}
// de lijst met fouten wordt samengesteld HTML
$modèle->erreurs = "";
foreach ($erreurs as $erreur) {
$modèle->erreurs .= "<li>$erreur</li>";
}
// menuopties
$modèle->optionsMenu = [
"Calcul de l'impôt" => "main.php?action=afficher-calcul-impot",
"Liste des simulations" => "main.php?action=lister-simulations",
"Fin de session" => "main.php?action=fin-session",];
// het sjabloon wordt teruggestuurd
return $modèle;
}
?>
<!-- document HTML -->
<!doctype html>
<html lang="fr">
<head>
…
</head>
<body>
…
</body>
</html>
Opmerkingen
- regels 19-32: berekening van het model [$modèle→erreurs] dat wordt gebruikt door de weergave [vue-erreurs.php];
- regels 34-37: berekening van het model [$modèle→optionsMenu] dat wordt gebruikt door het fragment [v-menu.php];
23.13.5.4. Tests [Postman]
De test [calculer-impot-3xx] levert de statuscode 338 op, wat geen verwachte statuscode is. Het antwoord HTML is dan als volgt:

23.13.6. Implementatie van de acties in het menu van de applicatie
Hier gaan we in op de implementatie van de menuacties. Laten we nog even de betekenis van de links die we zijn tegengekomen op een rijtje zetten
Weergave | Link | Doel | Functie |
Belastingberekening | [Liste des simulations] | [main.php?action=lister-simulations] | De lijst met simulaties opvragen |
[Fin de session] | [main.php?action=fin-session] | ||
Lijst met simulaties | [Calcul de l’impôt] | [main.php?action=afficher-calcul-impot] | Weergave van de belastingberekening |
[Fin de session] | [main.php?action=fin-session] | ||
Onverwachte fouten | [Calcul de l’impôt] | [main.php?action=afficher-calcul-impot] | Weergave van de belastingberekening |
[Liste des simulations] | [main.php?action=lister-simulations] | ||
[Fin de session] | [main.php?action=fin-session] |
Er moet worden opgemerkt dat een klik op een link een GET naar het doel van de link veroorzaakt. De acties [lister-simulations, fin-session] zijn geïmplementeerd met een bewerking GET, waardoor we ze als doelen van links kunnen instellen. Wanneer de actie via een POST wordt uitgevoerd, is het gebruik van een link niet meer mogelijk, tenzij deze wordt gekoppeld aan JavaScript.
Uit bovenstaande acties blijkt dat de actie [afficher-calcul-impot] nog niet is geïmplementeerd. Dit is een navigatiehandeling tussen twee weergaven: de server jSON of XML heeft geen reden om deze te implementeren, omdat zij het begrip ‘weergave’ niet kennen. Het is de server HTML die dit begrip introduceert.
We moeten dus de actie [afficher-calcul-impot] implementeren. Dit stelt ons in staat om de werkwijze voor het implementeren van een actie binnen de server te herzien.
Allereerst moeten we een nieuwe secundaire controller toevoegen. We noemen deze [AfficherCalculImpotController]:

Deze controller moet worden toegevoegd aan het configuratiebestand [config.json]:
{
"databaseFilename": "database.json",
"rootDirectory": "C:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-12",
"relativeDependencies": [
…
"/Controllers/InterfaceController.php",
"/Controllers/InitSessionController.php",
"/Controllers/ListerSimulationsController.php",
"/Controllers/AuthentifierUtilisateurController.php",
"/Controllers/CalculerImpotController.php",
"/Controllers/SupprimerSimulationController.php",
"/Controllers/FinSessionController.php",
"/Controllers/AfficherCalculImpotController.php"
],
"absoluteDependencies": [
"C:/myprograms/laragon-lite/www/vendor/autoload.php",
"C:/myprograms/laragon-lite/www/vendor/predis/predis/autoload.php"
],
…
"actions":
{
"init-session": "\\InitSessionController",
"authentifier-utilisateur": "\\AuthentifierUtilisateurController",
"calculer-impot": "\\CalculerImpotController",
"lister-simulations": "\\ListerSimulationsController",
"supprimer-simulation": "\\SupprimerSimulationController",
"fin-session": "\\FinSessionController",
"afficher-calcul-impot": "\\AfficherCalculImpotController"
},
…
"vues": {
"vue-authentification.php": [700, 221, 400],
"vue-calcul-impot.php": [200, 300, 341, 350, 800],
"vue-liste-simulations.php": [500, 600]
},
"vue-erreurs": "vue-erreurs.php"
}
- regel 15: de nieuwe controller;
- regel 30: de nieuwe actie en de bijbehorende controller;
- regel 35: de nieuwe controller retourneert statuscode 800. Bij een wisseling van weergave mag er geen fout optreden;
De controller [AfficherCalculImpotController.php] ziet er als volgt uit:
<?php
namespace Application;
// Symfony-afhankelijkheden
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Response;
class AfficherCalculImpotController implements InterfaceController {
// $config is de configuratie van de applicatie
// verwerking van een Request-verzoek
// maakt gebruik van de sessie en kan deze wijzigen
// $infos zijn aanvullende gegevens die specifiek zijn voor elke controller
// geeft een array terug: [$statusCode, $état, $content, $headers]
public function execute(
array $config,
Request $request,
Session $session,
array $infos = NULL): array {
// weergavewijziging – er hoeft alleen een statuscode te worden ingesteld
return [Response::HTTP_OK, 800, ["réponse" => ""], []];
}
}
Opmerkingen
- regel 10: net als de andere secundaire controllers implementeert de nieuwe controller de interface [InterfaceController];
- wijzigingen in de weergave zijn eenvoudig te implementeren: het volstaat om de statuscode te vermelden die bij de doelweergave hoort, in dit geval code 800 zoals hierboven besproken;
23.13.7. Testen in praktijkomstandigheden
De code is geschreven en elke actie is getest met [Postman]. Nu moeten we nog de opeenvolging van de weergaven in een praktijkomgeving testen. We hebben een manier nodig om de sessie HTML te initialiseren. We weten dat we de parameters [action=init-session&type=html] naar de server moeten sturen. Om te voorkomen dat we deze in de adresbalk van de browser moeten invoeren, voegen we het script [index.php] toe aan onze applicatie:

Het script [index.php] ziet er als volgt uit:
<?php
// omleiding naar [main.php] in de modus [html]
header('Location: main.php?action=init-session&type=html');
- regel 4: [header] is een functie PHP die een header HTTP aan het antwoord toevoegt. De header HTTP [Location: main.php?action=init-session&type=html] vraagt de clientbrowser om door te sturen naar het doel URL dat is opgegeven in [Location]. Het script [index.php] wordt opgevraagd met de URL en [http://localhost/php7/scripts-web/impots/version-12/index.php]. Wanneer de browser van de klant de omleiding ontvangt naar de relatieve URL vanuit [main.php?action=init-session&type=html], zal deze de absolute URL ([http://localhost/php7/scripts-web/impots/version-12/main.php?action=init-session&type=html]) opvragen en zal de sessie HTML starten;
De opstart-URL kan worden vereenvoudigd tot [http://localhost/php7/scripts-web/impots/version-12/]. Als er geen pagina is opgegeven in de URL, worden standaard de pagina's van de [index.html, index.php] gebruikt. Hier zal dus het script [index.php] worden gebruikt;
Laten we aan de slag gaan: we presenteren nu enkele reeksen weergaven.
In onze browser schakelen we het bijhouden van verzoeken in (F12 in Firefox) en vragen we de startpagina URL op via [https://localhost/php7/scripts-web/impots/version-12/]:

- in [4] is het eerste antwoord van de server een 302-omleiding:
- in [5] wordt een nieuw verzoek verzonden naar de URL [http://localhost/php7/scripts-web/impots/13/main.php?action=init-session&type=html];
Laten we de 302-omleiding eens nader bekijken:

- in [8] is de code HTTP [302] een omleidingscode: de clientbrowser krijgt te horen dat de opgevraagde URL is verplaatst. De nieuwe URL wordt gespecificeerd als [9]. De browser volgt deze omleiding met een nieuw verzoek GET:

- naar [12-13], het nieuwe verzoek van de browser;
Laten we het formulier invullen dat we hebben ontvangen;

Laten we dan eens een paar simulaties uitvoeren:


Laten we de lijst met simulaties opvragen:

Laten we de eerste simulatie verwijderen:

Laten we de sessie beëindigen:

De lezer wordt uitgenodigd om nog meer tests uit te voeren.
23.14. Client van de webservice jSON
23.14.1. Client-serverarchitectuur

We richten ons nu op de client jSON [A] van de webservice [B]. De client [A] heeft, net als de webservice [B], een gelaagde structuur:

Deze architectuur komt tot uiting in de volgende codestructuur:

De meeste klassen zijn al aan bod gekomen en uitgelegd:
link-paragraaf. | |
alinea-link. | |
alinea-link. | |
alinea-link. | |
alinea-link. | |
alinea-link. |
23.14.2. De laag [dao]

23.14.2.1. Interface
De interface van de laag [dao] zal als volgt zijn: [InterfaceClientDao.php]:
<?php
// naamruimte
namespace Application;
interface InterfaceClientDao {
// het uitlezen van belastinggegevens
public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;
// berekening van de belastingen van een belastingplichtige
public function calculerImpot(string $marié, int $enfants, int $salaire): Simulation;
// resultaten opslaan
public function saveResults(string $resultsFilename, array $simulations): void;
// authenticatie
public function authentifierUtilisateur(String $user, string $password): void;
// lijst met simulaties
public function listerSimulations(): array;
// een simulatie verwijderen
public function supprimerSimulation(int $numéro): array;
// sessie starten
public function initSession(string $type = 'json'): void;
// sessie beëindigen
public function finSession(): void;
}
Opmerkingen
- regel 9: met de methode [getTaxPayersData] kan het bestand jSON met belastingplichtigengegevens worden verwerkt. Deze methode wordt geïmplementeerd door de functie [TraitDao], die al is toegelicht (zie paragraaf ‘link’);
- regel 15: met de methode [saveResults] kunnen de resultaten van meerdere belastingberekeningen worden opgeslagen in een bestand jSON. Ook hier wordt deze methode geïmplementeerd door de reeds besproken trait [TraitDao] (zie paragraaf met link);
- regels 12, 18, 21, 27, 30: er is een methode aangemaakt voor elk van de acties die door de webservice worden geaccepteerd;
23.14.2.2. Implementatie
De interface [InterfaceClientDao] wordt geïmplementeerd door de volgende klasse [ClientDao]:
<?php
namespace Application;
// afhankelijkheden
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Response\CurlResponse;
class ClientDao implements InterfaceClientDao {
// gebruik van een Trait
use TraitDao;
// attributen
private $urlServer;
private $sessionCookie;
private $verbose;
// constructor
public function __construct(string $urlServer, bool $verbose = TRUE) {
$this->urlServer = $urlServer;
$this->verbose = $verbose;
}
…
}
Opmerkingen
- regels 18-21: de constructor ontvangt twee parameters:
- de URL [$urlServer] van de webservice jSON;
- een booleaanse waarde [$verbose] die bij TRUE aangeeft dat de klasse de antwoorden van de server op de console moet weergeven;
- regel 14: de sessiecookie. De functie hiervan is beschreven in versie 09 van de client (zie paragraaf ‘link’);
- regel 11: de klasse maakt gebruik van de eigenschap [TraitDao], die twee methoden van de interface implementeert:
- [getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array];
- [function calculerImpot(string $marié, int $enfants, int $salaire): Simulation];
23.14.2.2.1. Methode [initSession]
De methode [initSession] is als volgt geïmplementeerd:
public function initSession(string $type = 'json'): void {
// een client aanmaken HTTP
$httpClient = HttpClient::create();
// we sturen een verzoek naar de server zonder authenticatie
$response = $httpClient->request('GET', $this->urlServer,
["query" => [
"action" => "init-session",
"type" => $type
],
"verify_peer" => false
]);
// het antwoord wordt opgehaald
$this->getResponse($response);
// de sessiecookie wordt opgehaald
$headers = $response->getHeaders();
if (isset($headers["set-cookie"])) {
// sessiecookie?
foreach ($headers["set-cookie"] as $cookie) {
$match = [];
$match = preg_match("/^PHPSESSID=(.+?);/", $cookie, $champs);
if ($match) {
$this->sessionCookie = "PHPSESSID=" . $champs[1];
}
}
}
}
Aangezien de actie [init-session] de eerste actie moet zijn die aan de webservice wordt gevraagd, zal de methode [initSession] de eerste methode van de laag [dao] zijn die wordt aangeroepen.
Opmerkingen
- regel 1: het gewenste sessietype wordt als parameter doorgegeven. Als er geen parameter wordt opgegeven, wordt een jSON-sessie gestart;
- regels 5-11: er wordt een GET-verzoek naar de webservice verzonden;
- regels 7-8: de twee parameters van GET;
- regel 10: bij beveiligde communicatie (https-schema) wordt het door de webservice verzonden beveiligingscertificaat niet gecontroleerd;
- regel 13: de methode [getResponse] haalt het antwoord van de server op. Dit wordt weergegeven in de vorm van een array. Hier wordt het resultaat van de methode niet gebruikt. De methode [getResponse] genereert een uitzondering als de code HTTP van het antwoord van de webservice afwijkt van 200 OK;
- regels 14-25: aangezien de methode [initSession] de eerste methode van de laag [dao] is die wordt uitgevoerd, wordt de sessiecookie opgehaald zodat de volgende methoden deze naar de webservice kunnen terugsturen. Deze code is al in versie 09 van commentaar voorzien;
23.14.2.2.2. De methode [getResponse]
De methode [getResponse] is verantwoordelijk voor het verwerken van het antwoord van de webservice:
private function getResponse(CurlResponse $response) {
// het antwoord wordt opgehaald
$json = $response->getContent(false);
// logbestanden
if ($this->verbose) {
print "$json\n";
}
// de status van het antwoord wordt opgehaald
$statusCode = $response->getStatusCode();
// fout?
if ($statusCode !== 200) {
// er is een fout opgetreden
throw new ExceptionImpots($json);
}
// we sturen ons antwoord terug
$array = json_decode($json, true);
return $array["réponse"];
}
Opmerkingen
- regel 1: de methode is privé;
- regel 1: de parameter van de methode is het antwoord van de webservice van het type [Symfony\Component\HttpClient\Response\CurlResponse], het Symfony-antwoordtype, wanneer [HttpClient] wordt geïmplementeerd door [CurlClient], d.w.z. door de bibliotheek [curl];
- regel 3: het antwoord jSON wordt van de server opgehaald. Ter herinnering: de parameter [false] is bedoeld om te voorkomen dat Symfony een uitzondering genereert wanneer de status van het antwoord HTTP van de server binnen het bereik van [3xx, 4xx, 5xx] valt;
- regels 5-7: als we in de modus [$verbose] zitten, geven we het antwoord van de server weer op de console;
- regels 9-14: als de status van het HTTP-antwoord van de server niet 200 is, dan wordt er een uitzondering gegenereerd met als foutmelding het jSON-antwoord van de server;
- regel 16: de tekenreeks jSON wordt gedecodeerd in een array;
- regel 17: de relevante informatie bevindt zich in [$array["réponse"]];
23.14.2.2.3. De methode [authentifierUtilisateur]
De methode [authentifierUtilisateur] is als volgt:
public function authentifierUtilisateur(string $user, string $password): void {
// we maken een client aan HTTP
$httpClient = HttpClient::create();
// we sturen het verzoek naar de server met authenticatie
$response = $httpClient->request('POST', $this->urlServer,
["query" => [
"action" => "authentifier-utilisateur"
],
"body" => [
"user" => $user,
"password" => $password
],
"verify_peer" => false,
"headers" => ["Cookie" => $this->sessionCookie]
]);
// we halen het antwoord op
$this->getResponse($response);
}
Opmerkingen
- regel 5: de aanvraag van de klant is een POST;
- regels 6-8: parameters in URL;
- regels 9-12: parameters van POST;
- regel 14: de sessiecookie;
- regel 17: het antwoord wordt gelezen. We weten dat in geval van een fout (HTTP-code anders dan 200) de methode [getResponse] zelf een uitzondering genereert;
23.14.2.2.4. De methode [calculerImpot]
public function calculerImpot(string $marié, int $enfants, int $salaire): Simulation {
// er wordt een client aangemaakt HTTP
$httpClient = HttpClient::create();
// er wordt een verzoek naar de server gestuurd zonder authenticatie, maar met de sessiecookie
$response = $httpClient->request('POST', $this->urlServer,
["query" => [
"action" => "calculer-impot"],
"body" => [
"marié" => $marié,
"enfants" => $enfants,
"salaire" => $salaire
],
"verify_peer" => false,
"headers" => ["Cookie" => $this->sessionCookie]
]);
// we ontvangen het antwoord
$array = $this->getResponse($response);
return (new Simulation())->setFromArrayOfAttributes($array);
}
Opmerkingen
- regel 6-7: de enige parameter van URL;
- regels 8-12: de drie parameters van de methode POST (regel 5);
- regel 17: het antwoord wordt verwerkt;
- regel 18: als we hier zijn aangekomen, betekent dit dat de methode [getResponse] geen uitzondering heeft gegenereerd. We retourneren een [Simulation]-object dat is geïnitialiseerd met de array die door [getResponse] is geretourneerd;
23.14.2.2.5. De methode [listerSimulations]
public function listerSimulations(): array {
// we maken een client aan: HTTP
$httpClient = HttpClient::create();
// er wordt een verzoek naar de server gestuurd zonder authenticatie, maar met de sessiecookie
$response = $httpClient->request('GET', $this->urlServer,
["query" => [
"action" => "lister-simulations"
],
"verify_peer" => false,
"headers" => ["Cookie" => $this->sessionCookie]
]);
// we ontvangen het antwoord
return $this->getSimulations($response);
}
Opmerkingen
- regel 5: methode GET;
- regels 6-8: de enige parameter van GET;
- regel 13: het ophalen van de simulaties wordt toevertrouwd aan de privé-methode [getSimulations];
23.14.2.2.6. De methode [getSimulations]
private function getSimulations(CurlResponse $response): array {
// we halen het antwoord op JSON
$array = $this->getResponse($response);
// we hebben een associatieve objectenarray
// we maken er een array van Simulation-objecten van
$simulations = [];
foreach ($array as $simulation) {
$simulations [] = (new Simulation())->setFromArrayOfAttributes($simulation);
}
// we geven de lijst met simulatieobjecten weer
return $simulations;
}
Opmerkingen
- regel 3: de array uit het antwoord wordt opgehaald. Dit is een array van arrays, waarbij elk van deze arrays alle attributen van een [Simulation]-object heeft;
- regel 6: als we hier terechtkomen, betekent dit dat de methode [getResponse] geen uitzondering heeft gegenereerd;
- regels 6-9: we gebruiken het antwoord om een array van [Simulation]-objecten samen te stellen;
- regel 11: deze array wordt teruggegeven;
23.14.2.2.7. De methode [SupprimerSimulation]
public function supprimerSimulation(int $numéro): array {
// we maken een client aan HTTP
$httpClient = HttpClient::create();
// we sturen de verzoek naar de server zonder authenticatie, maar met de sessiecookie
$response = $httpClient->request('GET', $this->urlServer,
["query" => [
"action" => "supprimer-simulation",
"numéro" => $numéro
],
"verify_peer" => false,
"headers" => ["Cookie" => $this->sessionCookie]
]);
// het antwoord wordt opgehaald
return $this->getSimulations($response);
}
Opmerkingen
- regel 5: er wordt een verzoek GET verzonden;
- regels 6-9: de twee parameters van de URL;
- regel 14: na een verwijdering geeft de server de nieuwe tabel met simulaties terug. Deze tabel wordt weergegeven;
23.14.2.2.8. De methode [finSession]
Een werksessie met de webservice wordt normaal gesproken afgesloten met het aanroepen van de methode [finSession]:
public function finSession(): void {
// we maken een client aan: HTTP
$httpClient = HttpClient::create();
// er wordt een verzoek naar de server gestuurd zonder authenticatie, maar met de sessiecookie
$response = $httpClient->request('GET', $this->urlServer,
["query" => [
"action" => "fin-session"
],
"verify_peer" => false,
"headers" => ["Cookie" => $this->sessionCookie]
]);
// we ontvangen het antwoord
$this->getResponse($response);
}
Opmerkingen
- regel 5: er wordt een verzoek GET verzonden;
- regels 6-8: de enige parameter van de URL;
- regel 13: het antwoord wordt gelezen. Er wordt een uitzondering gegenereerd als de HTTP-code van het antwoord niet 200 is;
23.14.3. De laag [métier]

23.14.3.1. De interface
De interface van de laag [métier] is als volgt: [InterfaceClientMetier.php]:
<?php
// naamruimte
namespace Application;
interface InterfaceClientMetier {
// berekening van de belastingen van een belastingplichtige
public function calculerImpot(string $marié, int $enfants, int $salaire): Simulation;
// berekening van de belastingen in batchmodus
public function executeBatchImpots(string $taxPayersFileName, string $resultsFilename, string $errorsFileName): void;
// authenticatie
public function authentifierUtilisateur(String $user, string $password): void;
// lijst met simulaties
public function listerSimulations(): array;
// resultaten opslaan
public function saveResults(string $resultsFilename, array $simulations): void;
// een simulatie verwijderen
public function supprimerSimulation(int $numéro): array;
// sessie starten
public function initSession(string $type = 'json'): void;
// einde sessie
public function finSession(): void;
}
Opmerkingen
- alleen de methode [executeBatchImpots] op regel 12 is specifiek voor de laag [métier]. Alle andere behoren tot de laag [dao], die ze implementeert;
23.14.3.2. De klasse [ClientMetier]
De klasse die de laag [métier] implementeert, is de volgende:
<?php
namespace Application;
class ClientMetier implements InterfaceClientMetier {
// attribuut
private $clientDao;
// fabrikant
public function __construct(InterfaceClientDao $clientDao) {
$this->clientDao = $clientDao;
}
// belastingberekening
public function calculerImpot(string $marié, int $enfants, int $salaire): Simulation {
return $this->clientDao->calculerImpot($marié, $enfants, $salaire);
}
// belastingberekening in batchmodus
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
// uitzonderingen uit de laag [dao] doorgeven
// de belastingplichtigengegevens worden opgehaald
$taxPayersData = $this->clientDao->getTaxPayersData($taxPayersFileName, $errorsFileName);
// resultatenoverzicht
$simulations = [];
// de gegevens worden verwerkt
foreach ($taxPayersData as $taxPayerData) {
// de belasting wordt berekend
$simulations [] = $this->calculerImpot(
$taxPayerData->getMarié(),
$taxPayerData->getEnfants(),
$taxPayerData->getSalaire());
}
// de resultaten worden opgeslagen
if ($resultsFileName !== NULL) {
$this->clientDao->saveResults($resultsFileName, $simulations);
}
}
public function authentifierUtilisateur(String $user, string $password): void {
$this->clientDao->authentifierUtilisateur($user, $password);
}
public function listerSimulations(): array {
return $this->clientDao->listerSimulations();
}
public function saveResults(string $resultsFilename, array $simulations): void {
$this->clientDao->saveResults($resultsFilename, $simulations);
}
public function supprimerSimulation(int $numéro): array {
return $this->clientDao->supprimerSimulation($numéro);
}
public function finSession(): void {
$this->clientDao->finSession();
}
public function initSession(string $type = 'json'): void {
$this->clientDao->initSession($type);
}
}
Opmerkingen
- regels 10-12: om te worden geconstrueerd, heeft de laag [métier] een verwijzing naar de laag [dao] nodig;
- regels 20-38: alleen de methode [executeBatchImpots] is specifiek voor de laag [métier]. De implementatie van de andere methoden delegeert het uit te voeren werk aan de methoden met dezelfde naam in de laag [dao];
- regel 23: er wordt een beroep gedaan op de laag [dao] om in een array van objecten van het type [TaxPayerData] de gegevens van de belastingplichtigen op te halen;
- regel 25: de verschillende berekende simulaties worden samengevoegd in de tabel [$simulations];
- regels 27-33: de belasting voor elke belastingplichtige in de tabel [$taxPayersData] wordt berekend;
- regels 35-37: de resultaten uit de tabel [$simulations] worden opgeslagen in een bestand jSON;
Opmerking: De laag [métier] doet vrijwel niets. Men zou kunnen besluiten deze te verwijderen en alles samen te voegen in de laag [dao].
23.14.4. Het hoofdscript

Het hoofdscript wordt geconfigureerd door het volgende bestand [config.json]:
{
"taxPayersDataFileName": "Data/taxpayersdata.json",
"resultsFileName": "Data/results.json",
"errorsFileName": "Data/errors.json",
"rootDirectory": "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-12",
"dependencies": [
"/Entities/BaseEntity.php",
"/Entities/TaxPayerData.php",
"/Entities/Simulation.php",
"/Entities/ExceptionImpots.php",
"/Utilities/Utilitaires.php",
"/Model/InterfaceClientDao.php",
"/Model/TraitDao.php",
"/Model/ClientDao.php",
"/Model/InterfaceClientMetier.php",
"/Model/ClientMetier.php"
],
"absoluteDependencies": [
"C:/myprograms/laragon-lite/www/vendor/autoload.php"
],
"user": {
"login": "admin",
"passwd": "admin"
},
"urlServer": "https://localhost:443/php7/scripts-web/impots/version-12/main.php"
}
Het hoofdscript [main.php] is als volgt:
<?php
// strikte naleving van de opgegeven typen van de functieparameters
declare(strict_types = 1);
// naamruimte
namespace Application;
// foutbeheer door PHP
// ini_set("display_errors", "0");
//
// pad naar het configuratiebestand
define("CONFIG_FILENAME", "../Data/config.json");
// de configuratie wordt opgehaald
$config = \json_decode(file_get_contents(CONFIG_FILENAME), true);
// de benodigde afhankelijkheden worden in het script opgenomen
$rootDirectory = $config["rootDirectory"];
foreach ($config["dependencies"] as $dependency) {
require "$rootDirectory/$dependency";
}
// absolute afhankelijkheden (bibliotheken van derden)
foreach ($config["absoluteDependencies"] as $dependency) {
require "$dependency";
}
// definitie van constanten
define("TAXPAYERSDATA_FILENAME", "$rootDirectory/{$config["taxPayersDataFileName"]}");
define("RESULTS_FILENAME", "$rootDirectory/{$config["resultsFileName"]}");
define("ERRORS_FILENAME", "$rootDirectory/{$config["errorsFileName"]}");
//
// Symfony-afhankelijkheden
use Symfony\Component\HttpClient\HttpClient;
// aanmaken van de laag [dao]
$clientDao = new ClientDao($config["urlServer"]);
// aanmaken van de laag [métier]
$clientMetier = new ClientMetier($clientDao);
// berekening van de belastingen in batchmodus
try {
// initialisatie van de sessie
$clientMetier->initSession('json');
// authenticatie
$clientMetier->authentifierUtilisateur($config["user"]["login"], $config["user"]["passwd"]);
// berekening van belastingen zonder de resultaten op te slaan
$clientMetier->executeBatchImpots(TAXPAYERSDATA_FILENAME, NULL, ERRORS_FILENAME);
// lijst met simulaties
$clientMetier->listerSimulations();
// verwijdering van een simulatie
$simulations = $clientMetier->supprimerSimulation(1);
// resultaten opslaan
$clientMetier->saveResults(RESULTS_FILENAME, $simulations);
// sessie beëindigen
$clientMetier->finSession();
// actie zonder te zijn ingelogd – moet crashen
$clientMetier->listerSimulations();
} catch (ExceptionImpots $ex) {
// de foutmelding wordt weergegeven
print "Une erreur s'est produite : " . $ex->getMessage() . "\n";
}
// einde
print "Terminé\n";
exit();
Opmerkingen
- regels 12-16: gebruik van het configuratiebestand [config.json];
- regels 18-26: laden van alle afhankelijkheden;
- regels 28-34: definiëren van constanten en aliassen;
- regels 36-39: opbouw van de lagen [dao] en [métier];
- regel 44: initialisatie van een jSON-sessie;
- regel 46: authenticatie bij de server;
- regel 48: berekening van de belasting voor een reeks belastingplichtigen. De resultaten worden niet opgeslagen (2e parameter NULL);
- regel 50: de resultaten van al deze berekeningen worden opgevraagd;
- regel 52: simulatie nr. 1 (de tweede in de lijst) wordt verwijderd;
- regel 54: de resterende simulaties worden opgeslagen;
- regel 56: de sessie wordt beëindigd. Dit betekent dat de sessiecookie wordt verwijderd;
- regel 58: de lijst met simulaties wordt opgevraagd. Aangezien de sessiecookie is verwijderd, moet de authenticatie opnieuw worden uitgevoerd. Er moet dus een uitzondering optreden die aangeeft dat men niet is geauthenticeerd;
Het bestand [taxpayersdata.json] ziet er als volgt uit:
[
{
"marié": "oui",
"enfants": 2,
"salaire": 55555
},
{
"marié": "ouix",
"enfants": "2x",
"salaire": "55555x"
},
{
"marié": "oui",
"enfants": "2",
"salaire": 50000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 50000
},
{
"marié": "non",
"enfants": 2,
"salaire": 100000
},
{
"marié": "non",
"enfants": 3,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 5,
"salaire": 100000
},
{
"marié": "non",
"enfants": 0,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 2,
"salaire": 30000
},
{
"marié": "non",
"enfants": 0,
"salaire": 200000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 20000
}
]
Er zijn 12 belastingplichtigen, waarvan er 1 onjuist is. Dat zijn dus in totaal 11 simulaties. Eén daarvan wordt verwijderd. Er moeten er 10 overblijven.
Na uitvoering van het hoofdscript ziet het bestand jSON [results.json] er als volgt uit:
[
{
"marié": "oui",
"enfants": "2",
"salaire": "55555",
"impôt": 2814,
"surcôte": 0,
"décôte": 0,
"réduction": 0,
"taux": 0.14
},
{
"marié": "oui",
"enfants": "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
}
]
Er zijn inderdaad 10 simulaties.
Het bestand jSON [errors.json] heeft de volgende inhoud:
{
"numéro": 1,
"erreurs": [
{
"marié": "ouix"
},
{
"enfants": "2x"
},
{
"salaire": "55555x"
}
]
}
De console-uitvoer is als volgt (in de verbose-modus worden de antwoorden jSON van de server op de console weergegeven):
{"action":"init-session","état":700,"réponse":"session démarrée avec type [json]"}
{"action":"authentifier-utilisateur","état":200,"réponse":"Authentification réussie [admin, admin]"}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"2","salaire":"55555","impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"2","salaire":"50000","impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"3","salaire":"50000","impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"non","enfants":"2","salaire":"100000","impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"non","enfants":"3","salaire":"100000","impôt":16782,"surcôte":7176,"décôte":0,"réduction":0,"taux":0.41}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"3","salaire":"100000","impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"5","salaire":"100000","impôt":4230,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"non","enfants":"0","salaire":"100000","impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"2","salaire":"30000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"non","enfants":"0","salaire":"200000","impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"3","salaire":"20000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}}
{"action":"lister-simulations","état":500,"réponse":[{"marié":"oui","enfants":"2","salaire":"55555","impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"oui","enfants":"2","salaire":"50000","impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"50000","impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"non","enfants":"2","salaire":"100000","impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"non","enfants":"3","salaire":"100000","impôt":16782,"surcôte":7176,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"100000","impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3,"arrayOfAttributes":null},{"marié":"oui","enfants":"5","salaire":"100000","impôt":4230,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"non","enfants":"0","salaire":"100000","impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"oui","enfants":"2","salaire":"30000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0,"arrayOfAttributes":null},{"marié":"non","enfants":"0","salaire":"200000","impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"20000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0,"arrayOfAttributes":null}]}
{"action":"supprimer-simulation","état":600,"réponse":[{"marié":"oui","enfants":"2","salaire":"55555","impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"50000","impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"non","enfants":"2","salaire":"100000","impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"non","enfants":"3","salaire":"100000","impôt":16782,"surcôte":7176,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"100000","impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3,"arrayOfAttributes":null},{"marié":"oui","enfants":"5","salaire":"100000","impôt":4230,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"non","enfants":"0","salaire":"100000","impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"oui","enfants":"2","salaire":"30000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0,"arrayOfAttributes":null},{"marié":"non","enfants":"0","salaire":"200000","impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"20000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0,"arrayOfAttributes":null}]}
{"action":"fin-session","état":400,"réponse":"session supprimée"}
{"action":"lister-simulations","état":103,"réponse":["pas de session en cours. Commencer par action [init-session]"]}
Une erreur s'est produite : {"action":"lister-simulations","état":103,"réponse":["pas de session en cours. Commencer par action [init-session]"]}
Terminé
23.14.5. Tests [Codeception]
Net als bij eerdere clients kan de client van versie 12 worden onderworpen aan [Codeception]-tests:

De code van de testklasse van de [métier]-laag van de client is vergelijkbaar met die van de testklassen van eerdere clients:
<?php
// strikte naleving van de gedeclareerde typen van de functieparameters
declare (strict_types=1);
// naamruimte
namespace Application;
// definitie van constanten
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-12");
// pad naar het configuratiebestand
define("CONFIG_FILENAME", ROOT . "/Data/config.json");
// de configuratie wordt opgehaald
$config = \json_decode(\file_get_contents(CONFIG_FILENAME), true);
// de benodigde afhankelijkheden worden in het script opgenomen
$rootDirectory = $config["rootDirectory"];
foreach ($config["dependencies"] as $dependency) {
require "$rootDirectory$dependency";
}
// absolute afhankelijkheden (bibliotheken van derden)
foreach ($config["absoluteDependencies"] as $dependency) {
require "$dependency";
}
// Symfony-afhankelijkheden
use Symfony\Component\HttpClient\HttpClient;
// testklasse
class ClientDaoTest extends \Codeception\Test\Unit {
// DAO-laag
private $clientDao;
public function __construct() {
parent::__construct();
// de configuratie ophalen
$config = \json_decode(\file_get_contents(CONFIG_FILENAME), true);
// aanmaken van de laag [dao]
$clientDao = new ClientDao($config["urlServer"]);
// aanmaken van de laag [métier]
$this->métier = new ClientMetier($clientDao);
// sessie initialiseren
$this->métier->initSession("json");
// authenticatie
$this->métier->authentifierUtilisateur("admin", "admin");
}
// tests
public function test1() {
$simulation = $this->métier->calculerImpot("oui", 2, 55555);
$this->assertEqualsWithDelta(2815, $simulation->getImpôt(), 1);
$this->assertEqualsWithDelta(0, $simulation->getSurcôte(), 1);
$this->assertEqualsWithDelta(0, $simulation->getDécôte(), 1);
$this->assertEqualsWithDelta(0, $simulation->getRéduction(), 1);
$this->assertEquals(0.14, $simulation->getTaux());
}
public function test2() {
….
}
…
public function test11() {
…
}
}
Opmerkingen
- regels 34-46: ter herinnering: de constructor van de testklasse wordt vóór elke test uitgevoerd;
- regels 38-41: aanmaak van de lagen [dao] en [métier];
- regels 42-45: de testmethoden [test1…, test11] testen de methode [calculerImpot]. Om dit mogelijk te maken, moet eerst een sessie jSON worden geïnitialiseerd en moet men zich authenticeren;
De testresultaten zijn als volgt:

Er zouden nog veel meer tests moeten worden uitgevoerd:
- de verschillende methoden van de laag [dao] testen;
- de door de webserver geretourneerde statussen testen. Deze statussen zijn belangrijk omdat hun waarde bepaalt welke pagina HTML moet worden weergegeven;