22. Anwendungsübung – Version 11
Es kommt immer noch häufig vor, dass Webdienste ihre Antwort in Form eines XML-Streams statt eines jSON-Streams senden:
- Der Datenstrom jSON ist schlanker, erfordert jedoch eine Anleitung, um ihn zu verstehen;
- der Datenstrom XML ist ausführlicher, aber selbsterklärend. Er ist sofort verständlich;
Wir passen die Client-/Server-Version 11 so an, dass der Server nun einen Datenstrom vom Typ XML als Antwort an seine Clients sendet:

22.1. Der Server

Diese Architektur wird durch die folgenden Skripte implementiert:

22.1.1. Die Klasse [Utilitaires]
Wir greifen auf die Klasse [Utilitaires] zurück, die bereits ab Version 03 verwendet wird (siehe Abschnitt „Link“):
<?php
// Namensraum
namespace Application;
// eine Klasse von Hilfsfunktionen
abstract class Utilitaires {
public static function cutNewLinechar(string $ligne): string {
…
}
// aus https://stackoverflow.com/questions/1397036/how-to-convert-array-to-simplexml
public static function getXmlForArrayOfAttributes(array $arrayOfAttributes,
\SimpleXmlElement &$node): void {
// Die Attribute des Arrays werden durchlaufen
foreach ($arrayOfAttributes as $attribute => $value) {
// Ist das Attribut numerisch?
if (is_numeric($attribute)) {
// Fall des Array-Index (aber auch andere Fälle)
$attribute = 'i' . $attribute;
}
// Ist $value ein Array?
if (is_array($value)) {
// Nun wird das Array [$value] untersucht
// Wir fügen dem Graphen XML einen Knoten hinzu
$subnode = $node->addChild($attribute);
// Rekursiver Aufruf, um das Array [$value] zu untersuchen
Utilitaires::getXmlForArrayOfAttributes($value, $subnode);
} else {
// Der Knoten wird zum Graphen XML hinzugefügt
$node->addChild("$attribute", htmlspecialchars("$value"));
}
}
}
}
Kommentare
- Zeilen 14–36: Wir definieren die statische Methode [getXmlForArrayOfAttributes], die die Zeichenkette XML aus einem als Parameter übergebenen Array [arrayOfAttributes] zurückgibt. Der zweite Parameter ist die Referenz eines Knotens eines Graphen XML vom Typ [SimpleXmlElement]. Nach der Ausführung enthält dieser Knoten den Graphen XML aus dem Array [arrayOfAttributes];
Wir schreiben den folgenden Test „[testXml.php]“:

<?php
// Abhängigkeit
require __DIR__ . "/Utilitaires.php";
// Assoziatives Array
$array = ["nom" => "amédée", "prénom" => "sylvain", "âge" => 40,
"enfants" => [["nom" => "amédée", "prénom" => "béatrice", "âge" => 6],
["nom" => "amédée", "prénom" => "bertrand", "âge" => 4]]];
// XML
header("Content-Type: application/xml");
$node = new \SimpleXMLElement("<?xml version='1.0' encoding='UTF-8'?><root></root>");
\Application\Utilitaires::getXmlForArrayOfAttributes($array, $node);
print $node->asXML();
Wenn wir dieses Skript [2] ausführen, erhalten wir im Chrome-Browser folgendes Ergebnis:

22.1.2. Das Serverskript
Das Serverskript [impots-server.php] muss ebenso wie seine Konfigurationsdatei [config-server.json] geändert werden:
{
"rootDirectory": "C:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-11",
"databaseFilename": "Data/database.json",
"relativeDependencies": [
"/../version-08/Entities/BaseEntity.php",
"/../version-08/Entities/ExceptionImpots.php",
"/../version-08/Entities/TaxAdminData.php",
"/../version-08/Entities/Database.php",
"/../version-08/Dao/InterfaceServerDao.php",
"/../version-08/Dao/ServerDao.php",
"/../version-09/Dao/ServerDaoWithSession.php",
"/../version-08/Métier/InterfaceServerMetier.php",
"/../version-08/Métier/ServerMetier.php",
"/../version-09/Utilities/Logger.php",
"/../version-09/Utilities/SendAdminMail.php",
"/Utilities/Utilitaires.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": "Data/logs.txt"
}
Anmerkungen
- Das Projektverzeichnis ist nun der Ordner der Version 11;
- Zeile 16: Die neue Klasse [Utilitaires] wird eingebunden;
Die Änderungen am Server-Skript lauten wie folgt:
<?php
// Strikte Einhaltung der deklarierten Typen der Funktionsparameter
declare (strict_types=1);
// Namensraum
namespace Application;
…
// Vorbereitung der Antwort JSON vom Server
$response = new Response();
$response->headers->set("content-type", "application/xml");
$response->setCharset("utf-8");
…
// Erstellung der Schicht [métier]
$métier = new ServerMetier($dao);
// Berechnung der Steuer
$result = $métier->calculerImpot($marié, (int) $enfants, (int) $salaire);
// Die Antwort wird übermittelt
sendResponse($response, $result, Response::HTTP_OK, [], $logger, $redis);
// Ende
exit;
function doInternalServerError(string $message, Response $response, array $infos,
…
}
// Funktion zum Senden der Antwort HTTP an den Kunden
function sendResponse(Response $response, array $result, int $statusCode,
array $headers, Logger $logger = NULL, \Predis\Client $predisClient = NULL) {
// $response: Antwort HTTP
// $result: Ergebnistabelle
// $statusCode: Status HTTP der Antwort
// $headers: HTTP-Kopfzeilen, die in die Antwort aufgenommen werden sollen
// $logger: der Logger der Anwendung
// $predisClient: ein Client [predis]
//
// Status HTTTP
$response->setStatusCode($statusCode);
// Hauptteil XML
$node = new \SimpleXMLElement("<?xml version='1.0' encoding='UTF-8'?><réponse></réponse>");
Utilitaires::getXmlForArrayOfAttributes($result, $node);
$response->setContent($node->asXML());
// Header
$response->headers->add($headers);
// Versand
$response->send();
// Protokoll
if ($logger != NULL) {
// Protokoll in jSON
$log = \json_encode(["réponse" => $result], JSON_UNESCAPED_UNICODE);
$logger->write("$log\n");
$logger->close();
}
// Verbindung schließen [redis]
if ($predisClient != NULL) {
$predisClient->disconnect();
}
}
Kommentare
- Zeile 12: Es wird angegeben, dass die Antwort vom Typ [application/xml] ist;
- Zeilen 29–59: Die Serverantwort lautet nun XML;
- Zeile 41: Erstellung des Wurzelknotens [<réponse></réponse>] des Graphen XML;
- Zeile 42: Dieser Graph wird um den Graphen XML aus der Tabelle [$result] mit den an den Kunden zu sendenden Ergebnissen ergänzt;
- Zeile 43: Der Graph XML wird zur Übermittlung an den Kunden in die Zeichenkette XML umgewandelt;
Test
Direkt in einem Chrome-Browser gibt man „URL [http://localhost/php7/scripts-web/impots/version-11/impots-server.php?mari%C3%A9=oui&enfants=2&salaire=60000]“ ein. Man erhält das folgende Ergebnis „[1]“ in einem Chrome-Browser:

22.2. Der Kunde
Wir wenden uns nun dem clientseitigen Teil der Anwendung zu.

Diese Architektur wird durch die folgenden Skripte implementiert:

In der neuen Version ändern sich lediglich:
- die Konfigurationsdatei [config-client.json];
- die Client-Schicht [dao];
Die Konfigurationsdatei [config-client.json] sieht nun wie folgt aus:
{
"rootDirectory": "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-11",
"taxPayersDataFileName": "Data/taxpayersdata.json",
"resultsFileName": "Data/results.json",
"errorsFileName": "Data/errors.json",
"dependencies": [
"/../version-08/Entities/BaseEntity.php",
"/../version-08/Entities/TaxPayerData.php",
"/../version-08/Entities/ExceptionImpots.php",
"/../version-08/Utilities/Utilitaires.php",
"/../version-08/Dao/InterfaceClientDao.php",
"/../version-08/Dao/TraitDao.php",
"/Dao/ClientDao.php",
"/../version-08/Métier/InterfaceClientMetier.php",
"/../version-08/Métier/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-11/impots-server.php"
}
22.2.1. Die Schicht [dao]
Der Client [ClientDao.php] (Zeile 13 oben) wird angepasst, um das neue Antwortformat zu berücksichtigen. Zur Verarbeitung der Antwort wird [simpleXML] verwendet:
<?php
namespace Application;
// Abhängigkeiten
use \Symfony\Component\HttpClient\HttpClient;
class ClientDao implements InterfaceClientDao {
// Verwendung eines Traits
use TraitDao;
// Attribute
private $urlServer;
private $user;
private $sessionCookie;
// Konstruktor
public function __construct(string $urlServer, array $user) {
$this->urlServer = $urlServer;
$this->user = $user;
}
// Steuerberechnung
public function calculerImpot(string $marié, int $enfants, int $salaire): array {
…
// Die Antwort wird abgerufen XML
$réponse = $response->getContent(false);
$xml = new \SimpleXMLElement($réponse);
// Protokolle
// print "$réponse\n";
// der Status der Antwort wird abgerufen
$statusCode = $response->getStatusCode();
// Fehler?
if ($statusCode !== 200) {
// Es liegt ein Fehler vor – es wird eine Ausnahme ausgelöst
$message = \json_encode(["statut HTTP" => $statusCode, "réponse" => $xml], JSON_UNESCAPED_UNICODE);
throw new ExceptionImpots($message);
}
if (!$this->sessionCookie) {
// Das Session-Cookie wird abgerufen
$headers = $response->getHeaders();
if (isset($headers["set-cookie"])) {
// Sitzungs-Cookie?
foreach ($headers["set-cookie"] as $cookie) {
$match = [];
$match = preg_match("/^PHPSESSID=(.+?);/", $cookie, $champs);
if ($match) {
$this->sessionCookie = "PHPSESSID=" . $champs[1];
}
}
}
}
// Die Antwort wird als Array zurückgegeben
return \json_decode(\json_encode($xml, JSON_UNESCAPED_UNICODE), true);
}
}
Anmerkungen
- Zeilen 26–27: Die Antwort des Servers wird gelesen. Es handelt sich um ein Dokument XML [<réponse>…</réponse>]. Aus dem empfangenen Dokument XML wird ein Objekt [SimpleXMLElement] erstellt;
- Zeilen 33–37: Im Fehlerfall lautet die Ausnahmemeldung die Zeichenfolge „jSON“ aus der Serverantwort und nicht die erfasste Zeichenfolge „XML“. Die Zeichenfolge „jSON“ ist nämlich prägnanter;
- Zeile 53: Das Ergebnisarray wird in zwei Schritten zurückgegeben:
- Das Objekt [$xml] vom Typ [\SimpleXMLElement] wird in jSON umgewandelt;
- die erhaltene Zeichenfolge jSON wird in ein assoziatives Array umgewandelt. Dies ist das zurückzugebende Ergebnis;
Test
Wenn man den Client in einer korrekten Umgebung (Datenbank, Authentifizierung, Protokolle) startet, erhält man die üblichen Ergebnisse (überprüfen Sie die Dateien [taxpayersdata.json, results.txt, errors.json]). Auf der Serverseite lauten die Protokolle wie folgt:
06/07/19 07:41:32:877 :
---nouvelle requête
06/07/19 07:41:32:882 : Autentification en cours…
06/07/19 07:41:32:883 : Authentification réussie [admin, admin]
06/07/19 07:41:32:883 : paramètres ['marié'=>oui, 'enfants'=>2, 'salaire'=>55555] valides
06/07/19 07:41:32:908 : données fiscales prises en base de données
06/07/19 07:41:32:959 : {"réponse":{"impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}}
06/07/19 07:41:33:070 :
---nouvelle requête
06/07/19 07:41:33:077 : Authentification prise en session…
06/07/19 07:41:33:077 : paramètres ['marié'=>oui, 'enfants'=>2, 'salaire'=>50000] valides
06/07/19 07:41:33:099 : données fiscales prises dans redis
06/07/19 07:41:33:100 : {"réponse":{"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}}
06/07/19 07:41:33:189 :
---nouvelle requête
06/07/19 07:41:33:202 : Authentification prise en session…
06/07/19 07:41:33:202 : paramètres ['marié'=>oui, 'enfants'=>3, 'salaire'=>50000] valides
06/07/19 07:41:33:233 : données fiscales prises dans redis
06/07/19 07:41:33:233 : {"réponse":{"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}}
06/07/19 07:41:33:318 :
…
22.2.2. Tests [Codeception]

Der Test [ClientMetierTest] lautet wie folgt:
<?php
// Strikte Einhaltung der deklarierten Typen der Funktionsparameter
declare (strict_types=1);
// Namensraum
namespace Application;
// Definition von Konstanten
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-11");
// Pfad zur Konfigurationsdatei
define("CONFIG_FILENAME", ROOT . "/Data/config-client.json");
// die Konfiguration wird abgerufen
$config = \json_decode(file_get_contents(CONFIG_FILENAME), true);
…
// Testklasse
class ClientMetierTest extends Unit {
// Geschäftslogikschicht
private $métier;
public function __construct() {
parent::__construct();
// Konfiguration wird abgerufen
$config = \json_decode(\file_get_contents(CONFIG_FILENAME), true);
// Erstellung der Schicht [dao]
$clientDao = new ClientDao($config["urlServer"], $config["user"]);
// Erstellung der Schicht [métier]
$this->métier = new ClientMetier($clientDao);
}
// Tests
…
}
Die Testergebnisse lauten wie folgt:
