Skip to content

20. Exercice d'application – version 10

La version précédente a montré que les données fiscales, partagées par tous les utilisateurs de l'application, devraient être stockées dans une mémoire de portée [application]. Nous allons utiliser un serveur Redis (https://redis.io) pour implémenter celle-ci.

20.1. Redis

Redis est un serveur qui garde en mémoire des données sous la forme de couples [clé, valeur]. Il est très rapide et très utilisé par les applications web comme cache, c'est-à-dire comme mémoire de données coûteuses à obtenir. La mémoire de portée [application] sera implémentée par un serveur Redis. Les scripts PHP ayant besoin de cette mémoire d'application seront des clients de ce serveur :

Image

  • en [1], un script PHP envoie une commande au serveur Redis, par exemple pour lui demander la valeur associée à une clé ;
  • en [2], le serveur Redis lui répond .

Le serveur Redis vit indépendamment des scripts PHP : les données qu'on lui confie restent disponibles d'une requête à l'autre, et pour tous les utilisateurs de l'application.

20.2. Installation de Redis

[Laragon] vient avec un serveur Redis non activé par défaut. On l'active dans les préférences de [Laragon] (menu [Preferences], onglet [Services & Ports]) en cochant la case [Redis]. On laisse le port [6379] que les clients Redis utilisent par défaut. Le serveur Redis est alors lancé avec les autres services de [Laragon] (Apache, MySQL...) par le bouton [Start All].

20.3. Le client Redis en mode commande

Le serveur Redis peut être interrogé en mode commande. On ouvre un terminal [Laragon] et on tape la commande [redis-cli], qui lance le client en mode commande du serveur Redis. Le client affiche alors l'invite [127.0.0.1:6379>] : il est connecté au serveur Redis de la machine locale (adresse IP 127.0.0.1), sur le port 6379. Voici une session de travail :

> redis-cli
127.0.0.1:6379> command count
(integer) 240
127.0.0.1:6379> set titre "PHP 8 par l'exemple"
OK
127.0.0.1:6379> get titre
"PHP 8 par l'exemple"
127.0.0.1:6379> exists titre
(integer) 1
127.0.0.1:6379> del titre
(integer) 1
127.0.0.1:6379> get titre
(nil)
127.0.0.1:6379> exists titre
(integer) 0
127.0.0.1:6379> quit
  • ligne 3 : le client Redis dispose de plus de 200 commandes pour dialoguer avec le serveur. L'une d'elles, [command count], affiche leur nombre exact pour la version utilisée ;
  • lignes 4-7 : nous n'allons présenter que celles dont nous allons avoir besoin dans notre application PHP. Nous allons utiliser Redis pour une unique chose : associer une valeur à une clé dans la mémoire de Redis. Cela se fait avec la commande Redis [set clé valeur]. La valeur peut ensuite être récupérée avec la commande [get clé]. C'est tout ce dont nous aurons besoin ;
  • lignes 8-9 : la commande [exists] permet de vérifier l'existence d'une clé : elle rend 1 si la clé existe, 0 sinon ;
  • lignes 10-11 : la commande [del] supprime une clé ;
  • lignes 12-13 : si on demande la valeur d'une clé inexistante, on obtient la valeur [nil] indiquant que la clé n'a pas été trouvée ;
  • ligne 16 : pour quitter le client Redis, on tape la commande [quit] .

Il peut être nécessaire de vider toute la mémoire de Redis. Cela se fait avec la commande [flushdb].

20.4. Installation d'un client Redis pour PHP

Il nous faut maintenant un client Redis pour PHP. Il en existe deux principaux :

  • l'extension PHP [phpredis], écrite en C, qu'il faut ajouter à la configuration de PHP ;
  • la bibliothèque [Predis] (https://github.com/predis/predis), écrite en PHP, qui s'installe avec [Composer] .

Comme le cours PHP 7, nous utiliserons [Predis], dans sa version 3. Elle figure dans le fichier [composer.json] du dossier [php8-oct-2026] et a été installée avec les autres bibliothèques du cours par la commande [composer install]. On la trouve dans le dossier [vendor/predis/predis]. Le chargeur de classes de [Composer] la prend en charge : il n'y a plus, comme dans le cours PHP 7, de fichier [predis/autoload.php] à inclure ni de méthode [Predis\Autoloader::register] à appeler.

20.5. Code du serveur

Le serveur de la version 10 est formé des fichiers suivants :

1
2
3
4
5
6
scripts-web/impots/version-10
├── Data
│   ├── .htaccess
│   ├── config-server.json
│   └── logs.txt
└── impots-server.php

Le fichier de configuration [config-server.json] évolue de la façon suivante :

{
    "databaseFilename": "../version-08/Data/database-mysql.json",
    "dependencies": [
        "../../../scripts-console/impots/version-05/Entities/ExceptionImpots.php",
        "../../../scripts-console/impots/version-05/Entities/TaxAdminData.php",
        "../../../scripts-console/impots/version-05/Entities/Database.php",
        "../../../scripts-console/impots/version-05/Utilities/Utilitaires.php",
        "../version-08/Dao/InterfaceServerDao.php",
        "../version-08/Dao/ServerDao.php",
        "../version-09/Dao/ServerDaoWithSession.php",
        "../version-08/Metier/InterfaceServerMetier.php",
        "../version-08/Metier/ServerMetier.php",
        "../version-09/Utilities/Logger.php",
        "../version-09/Utilities/SendAdminMail.php"
    ],
    "users": [
        {
            "login": "admin",
            "passwd": "admin"
        }
    ],
    "adminMail": {
        "smtp-server": "localhost",
        "smtp-port": 1025,
        "from": "guest@localhost",
        "to": "guest@localhost",
        "subject": "plantage du serveur de calcul d'impôts",
        "tls": false,
        "attachments": []
    },
    "logsFilename": "Data/logs.txt",
    "redis": {
        "scheme": "tcp",
        "host": "127.0.0.1",
        "port": 6379
    }
}

Commentaires

  • lignes 3-15 : la version 10 n'amène rien de nouveau en dehors du script [impots-server.php]. Elle utilise des éléments des versions 5, 8 et 9 ;
  • lignes 32-37 : les paramètres de connexion au serveur Redis : le protocole (tcp), la machine et le port. Ce sont les valeurs par défaut de [Predis], mais il vaut mieux les écrire dans la configuration : si le serveur Redis change de machine ou de port, seule la configuration est à modifier .

Le code du serveur [impots-server.php] évolue de la façon suivante :

<?php

// respect strict des types déclarés des paramètres et des résultats des fonctions
declare(strict_types=1);

// espace de noms
namespace Application;

// dépendances Symfony
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
// dépendances Predis
use Predis\Client as RedisClient;
use Predis\PredisException;
// alias de classe : la couche [dao] de la version 9 est utilisée avec Redis
use Application\ServerDaoWithSession as ServerDaoWithRedis;

// les erreurs de PHP ne sont pas envoyées au client (elles vont dans le journal du serveur web)
ini_set("display_errors", "0");

// le dossier racine du serveur : les chemins du fichier de configuration sont relatifs à ce dossier
const ROOT_DIRECTORY = __DIR__;
// chemin du fichier de configuration
const CONFIG_FILENAME = ROOT_DIRECTORY . "/Data/config-server.json";

// on récupère la configuration
$config = json_decode(file_get_contents(CONFIG_FILENAME), true, flags: JSON_THROW_ON_ERROR);

// on inclut les dépendances nécessaires au script
require_once ROOT_DIRECTORY . "/../../../vendor/autoload.php";
foreach ($config["dependencies"] as $dependency) {
    require_once ROOT_DIRECTORY . "/$dependency";
}

// session : elle ne contient plus que l'identifiant de l'utilisateur authentifié
$session = new Session();
$session->start();

// création du fichier des logs
try {
    $logger = new Logger(ROOT_DIRECTORY . "/" . $config["logsFilename"]);
} catch (ExceptionImpots $ex) {
    // erreur 500 sans logs - le message ne doit pas révéler l'arborescence des fichiers du serveur
    $message = "Echec lors de la création du fichier de logs [{$config["logsFilename"]}]";
    doInternalServerError($message, $config["adminMail"]);
    // terminé
    exit;
}

// 1er log
$logger->write("---nouvelle requête");

// on récupère la requête courante
$request = Request::createFromGlobals();
// authentification seulement la 1re fois
if (!$session->has("user")) {
    // log
    $logger->write("Authentification en cours…");
    // les identifiants envoyés par le client (null s'il n'en a pas envoyé)
    $requestUser = $request->getUser();
    $requestPassword = $request->getPassword();
    // l'utilisateur fait-il partie des utilisateurs autorisés ?
    $trouvé = $requestUser !== null && $requestPassword !== null
        && array_any(
            $config["users"],
            fn(array $user): bool => $user["login"] === $requestUser && hash_equals($user["passwd"], $requestPassword)
        );
    if (!$trouvé) {
        // pas trouvé - code 401
        sendResponse(
            ["erreur" => "Echec de l'authentification [$requestUser]"],
            Response::HTTP_UNAUTHORIZED,
            ["WWW-Authenticate" => 'Basic realm="Serveur de calcul des impots"'],
            $logger
        );
        // terminé
        exit;
    }
    // on note dans la session qu'on a authentifié l'utilisateur
    $session->set("user", $requestUser);
    // log
    $logger->write("Authentification réussie [$requestUser]");
} else {
    // log
    $logger->write("Authentification prise en session…");
}

// on a un utilisateur valide - on vérifie les paramètres reçus
$erreurs = [];
// on doit avoir une requête GET avec trois paramètres
if ($request->getMethod() !== "GET" || $request->query->count() !== 3) {
    $erreurs[] = "Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]";
}
// le statut marital
if (!$request->query->has("marié")) {
    $erreurs[] = "paramètre marié manquant";
} else {
    $marié = strtolower(trim($request->query->getString("marié")));
    if (!in_array($marié, ["oui", "non"], true)) {
        $erreurs[] = "paramètre marié [$marié] invalide";
    }
}
// le nombre d'enfants et le salaire : des nombres entiers >= 0
$valeurs = [];
foreach (["enfants", "salaire"] as $paramètre) {
    if (!$request->query->has($paramètre)) {
        $erreurs[] = "paramètre $paramètre manquant";
        continue;
    }
    $valeur = trim($request->query->getString($paramètre));
    if (preg_match('/^\d+$/', $valeur) !== 1) {
        $erreurs[] = "paramètre $paramètre [$valeur] invalide";
    } else {
        $valeurs[$paramètre] = (int) $valeur;
    }
}
// autres paramètres dans la requête ?
foreach (array_diff(array_keys($request->query->all()), ["marié", "enfants", "salaire"]) as $paramètre) {
    $erreurs[] = "paramètre [$paramètre] invalide";
}
// erreurs ?
if ($erreurs !== []) {
    // on envoie un code d'erreur 400 au client
    sendResponse(["erreurs" => $erreurs], Response::HTTP_BAD_REQUEST, [], $logger);
    // terminé
    exit;
}
// log
[$enfants, $salaire] = [$valeurs["enfants"], $valeurs["salaire"]];
$logger->write("paramètres ['marié'=>$marié, 'enfants'=>$enfants, 'salaire'=>$salaire] valides");

// on a tout ce qu'il faut pour travailler
// la mémoire de portée [application] : un serveur Redis
try {
    // le client Redis
    $redis = new RedisClient($config["redis"]);
    // on se connecte tout de suite au serveur pour savoir s'il est là
    $redis->connect();
} catch (PredisException $ex) {
    // erreur 500
    doInternalServerError("[redis], {$ex->getMessage()}", $config["adminMail"], $logger);
    // terminé
    exit;
}
// création de la couche [dao]
$json = $redis->get("taxAdminData");
if ($json === null) {
    // les données fiscales sont prises dans la base de données
    $logger->write("données fiscales prises en base de données");
    try {
        // construction de la couche [dao]
        $dao = new ServerDaoWithRedis(ROOT_DIRECTORY . "/" . $config["databaseFilename"]);
        // on met la chaîne JSON des données fiscales dans la mémoire de portée [application]
        $redis->set("taxAdminData", (string) $dao->getTaxAdminData());
    } catch (ExceptionImpots $ex) {
        // erreur 500
        doInternalServerError("[dao], {$ex->getMessage()}", $config["adminMail"], $logger, $redis);
        // terminé
        exit;
    }
} else {
    // les données fiscales sont prises dans la mémoire de portée [application]
    $taxAdminData = TaxAdminData::fromArray(json_decode($json, true, flags: JSON_THROW_ON_ERROR));
    // instanciation de la couche [dao]
    $dao = new ServerDaoWithRedis($taxAdminData);
    // log
    $logger->write("données fiscales prises dans Redis");
}
// création de la couche [métier]
$métier = new ServerMetier($dao);
// calcul de l'impôt
$result = $métier->calculerImpot($marié, $enfants, $salaire);
// on rend la réponse
sendResponse($result, Response::HTTP_OK, [], $logger, $redis);
// fin
exit;

// --------------------------------------------------------------------------
// erreur fatale : un mail à l'administrateur et une réponse 500 au client
function doInternalServerError(
    string $message,
    array $adminMail,
    ?Logger $logger = null,
    ?RedisClient $redis = null
): void {
    // le message d'erreur d'un pilote de SGBD peut ne pas être codé en UTF-8 (Windows-1252 sous Windows)
    if (!mb_check_encoding($message, "UTF-8")) {
        $message = mb_convert_encoding($message, "UTF-8", "Windows-1252");
    }
    // on envoie un mail à l'administrateur
    // [SendAdminMail] intercepte toutes les exceptions et les logue lui-même
    (new SendAdminMail($adminMail, $logger))->send($message);
    // on envoie un code d'erreur 500 au client
    sendResponse(["erreur" => $message], Response::HTTP_INTERNAL_SERVER_ERROR, [], $logger, $redis);
}

// envoi de la réponse HTTP au client
// $result : le tableau à envoyer, sous la forme ["réponse" => $result]
// $statusCode : le statut HTTP de la réponse
// $headers : des entêtes HTTP à ajouter à la réponse
// $logger : le journal de l'application (null s'il n'a pu être créé)
// $redis : le client Redis (null s'il n'a pas encore été créé)
function sendResponse(
    array $result,
    int $statusCode,
    array $headers = [],
    ?Logger $logger = null,
    ?RedisClient $redis = null
): void {
    // la réponse JSON
    $response = new JsonResponse(null, $statusCode, $headers);
    $response->setEncodingOptions(JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION);
    $response->setData(["réponse" => $result]);
    // envoi
    $response->send();
    // log
    $logger?->write($response->getContent());
    // fermeture de la connexion au serveur Redis
    $redis?->disconnect();
}

Commentaires

  • lignes 15-16 : les classes de [Predis] que nous utilisons. La classe [Predis\Client] est le client Redis. Nous lui donnons l'alias [RedisClient], plus parlant dans notre code. [PredisException] est la classe parent de toutes les exceptions lancées par [Predis] ;
  • ligne 18 : on donne l'alias [ServerDaoWithRedis] à la classe [ServerDaoWithSession] de la version 9 pour refléter le changement d'implémentation du script serveur. La classe [ServerDaoWithSession] sait construire la couche [dao] à partir de données fiscales déjà acquises : peu lui importe qu'elles viennent d'une session ou d'un serveur Redis ;
  • lignes 38-39 : la session est conservée. Nous avons ici deux informations à mémoriser :
    • le fait que l'utilisateur se soit authentifié correctement. Cette information est de portée [session] : elle est liée à un utilisateur précis et n'est pas valable pour les autres utilisateurs. Elle reste dans la session ;
    • les données de l'administration fiscale. Cette information est de portée [application] : elle n'est pas liée à un utilisateur précis mais est valable pour tous les utilisateurs. Elle va dans le serveur Redis ;
  • lignes 135-145 : création du client [Predis] qui va communiquer avec le serveur Redis, avec les paramètres de connexion du fichier de configuration :
    • ligne 140 : on connecte tout de suite le client au serveur pour savoir si celui-ci répond ;
    • si la connexion au serveur Redis échoue, on envoie une réponse d'erreur au client et un mail à l'administrateur de l'application. Le message d'erreur est préfixé par [[redis]] pour indiquer d'où vient l'erreur ;
  • ligne 148 : on demande au serveur Redis la valeur de la clé [taxAdminData]. Le client [Predis] offre une méthode pour chaque commande Redis : la méthode [get] envoie la commande [get taxAdminData]. Elle rend la valeur de la clé, ou [null] si elle n'existe pas ;
  • ligne 149 : si la clé n'est pas trouvée, alors les données fiscales sont prises en base de données ;
  • ligne 156 : la clé [taxAdminData] est placée dans la mémoire de Redis, associée à la chaîne JSON de l'objet [TaxAdminData] obtenu de la couche [dao]. La méthode [set] attend une chaîne de caractères pour la valeur de la clé. La conversion [(string)] appelle la méthode [__toString] de la classe [TaxAdminData], qui rend la chaîne JSON de l'objet. Le script PHP 7 laissait PHP faire cette conversion implicitement ; avec [declare(strict_types=1)], il vaut mieux l'écrire ;
  • ligne 159 : si la lecture de la base échoue, réponse 500 et mail à l'administrateur, le message étant préfixé par [[dao]] ;
  • lignes 164-169 : la clé [taxAdminData] est dans la mémoire de Redis. On sait que sa valeur est la chaîne JSON d'un objet [TaxAdminData] :
    • ligne 165 : on décode la chaîne JSON en tableau associatif, puis la méthode statique [TaxAdminData::fromArray] de la version 5 construit l'objet [TaxAdminData] à partir de ce tableau, en vérifiant ses données ;
    • ligne 167 : la couche [dao] est instanciée avec ces données ;
  • lignes 182-187 : la fonction [doInternalServerError] a un paramètre de plus, le client Redis, qu'elle transmet à la fonction [sendResponse]. Lorsqu'une fonction a de nombreux paramètres, on les écrit un par ligne ;
  • ligne 221 : la fonction [sendResponse] ferme la connexion au serveur Redis, si elle a été ouverte .

20.6. Code du client

La version 10 du client est identique à la version 9. Seul change le fichier de configuration [config-client.json] :

{
    "urlServer": "http://localhost/php8-oct-2026/scripts-web/impots/version-10/impots-server.php",
    "user": {
        "login": "admin",
        "passwd": "admin"
    },
    "httpClientOptions": {
        "verify_peer": false,
        "verify_host": false
    },
    "taxPayersDataFilename": "Data/taxpayersdata.json",
    "resultsFilename": "Data/results.json",
    "errorsFilename": "Data/errors.json",
    "dependencies": [
        "../version-05/Entities/ExceptionImpots.php",
        "../version-05/Entities/TaxPayerData.php",
        "../version-05/Utilities/Utilitaires.php",
        "../version-05/Dao/TraitDao.php",
        "../version-08/Dao/InterfaceClientDao.php",
        "../version-09/Dao/ClientDao.php",
        "../version-08/Metier/InterfaceClientMetier.php",
        "../version-08/Metier/ClientMetier.php"
    ]
}
  • ligne 2 : l'URL du serveur de la version 10 ;
  • ligne 20 : la couche [dao] du client est celle de la version 9 .

Le dossier [scripts-console/impots/version-10] contient donc seulement le dossier [Data], le script principal [Main/MainImpotsClient.php] (celui de la version 8) et le test [Tests/ClientMetierTest.php].

Avant d'exécuter le client, supprimons la clé [taxAdminData] de la mémoire du serveur Redis avec la commande [del taxAdminData] de [redis-cli]. Puis exécutons le client :

1
2
3
> php MainImpotsClient.php
Les résultats ont été enregistrés dans le fichier [Data/results.json]
Terminé

Les résultats sont les mêmes que dans la version 9. Les logs du serveur sont les suivants :

01/10/26 18:10:12.890 : ---nouvelle requête
01/10/26 18:10:12.891 : Authentification en cours…
01/10/26 18:10:12.891 : Authentification réussie [admin]
01/10/26 18:10:12.891 : paramètres ['marié'=>oui, 'enfants'=>2, 'salaire'=>55555] valides
01/10/26 18:10:12.907 : données fiscales prises en base de données
01/10/26 18:10:12.909 : {"réponse":{"impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}}
01/10/26 18:10:12.912 : ---nouvelle requête
01/10/26 18:10:12.912 : Authentification prise en session…
01/10/26 18:10:12.912 : paramètres ['marié'=>oui, 'enfants'=>2, 'salaire'=>50000] valides
01/10/26 18:10:12.913 : données fiscales prises dans Redis
01/10/26 18:10:12.913 : {"réponse":{"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}}
01/10/26 18:10:12.916 : ---nouvelle requête
…
  • ligne 5 : lors de la 1re requête, les données fiscales sont prises en base de données ;
  • ligne 10 : lors de la requête suivante, elles sont prises dans Redis .

Testons maintenant un nouveau cas d'erreur : le serveur Redis est absent. On peut arrêter le serveur Redis depuis [Laragon]. Nous avons plutôt mis le port 6380, sur lequel aucun serveur n'écoute, dans la configuration [redis] du serveur. Le résultat dans la console est le suivant :

1
2
3
> php MainImpotsClient.php
Erreur n° 42 : {"statut HTTP":500,"erreur":"[redis], Connection refused [tcp://127.0.0.1:6380]"}
Terminé

Les logs du serveur montrent que l'administrateur a été prévenu par un mail :

1
2
3
4
5
6
01/10/26 18:10:32.255 : ---nouvelle requête
01/10/26 18:10:32.255 : Authentification en cours…
01/10/26 18:10:32.256 : Authentification réussie [admin]
01/10/26 18:10:32.256 : paramètres ['marié'=>oui, 'enfants'=>2, 'salaire'=>55555] valides
01/10/26 18:10:32.305 : Message [[redis], Connection refused [tcp://127.0.0.1:6380]] envoyé à guest@localhost
01/10/26 18:10:32.305 : {"réponse":{"erreur":"[redis], Connection refused [tcp://127.0.0.1:6380]"}}

On remet ensuite le port 6379 dans la configuration.

20.7. Tests [PHPUnit] du client

La classe de test [ClientMetierTest] de la version 10 est identique à celle de la version 9, l'environnement du test étant celui du client de la version 10. Avant de commencer les tests, supprimons à l'aide du client [redis-cli] la clé [taxAdminData] de la mémoire du serveur Redis (commande [del taxAdminData]). Maintenant, exécutons le test :

> php vendor/bin/phpunit scripts-console/impots/version-10/Tests --testdox
PHPUnit 12.5.37 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.5.11
Configuration: .../php8-oct-2026/phpunit.xml

...........                                                       11 / 11 (100%)

Time: 00:00.063, Memory: 16.00 MB

Client Metier (Application\Tests\ClientMetier)
 ✔ Calculer impot with data set "couple, 2 enfants, 55555"
 ✔ Calculer impot with data set "couple, 2 enfants, 50000"
 ✔ Calculer impot with data set "couple, 3 enfants, 50000"
 ✔ Calculer impot with data set "célibataire, 2 enfants, 100000"
 ✔ Calculer impot with data set "célibataire, 3 enfants, 100000"
 ✔ Calculer impot with data set "couple, 3 enfants, 100000"
 ✔ Calculer impot with data set "couple, 5 enfants, 100000"
 ✔ Calculer impot with data set "célibataire, 0 enfant, 100000"
 ✔ Calculer impot with data set "couple, 2 enfants, 30000"
 ✔ Calculer impot with data set "célibataire, 0 enfant, 200000"
 ✔ Calculer impot with data set "couple, 3 enfants, 200000"

OK (11 tests, 22 assertions)

Examinons les logs [logs.txt] du serveur :

01/10/26 18:10:32.488 : ---nouvelle requête
01/10/26 18:10:32.488 : Authentification en cours…
01/10/26 18:10:32.488 : Authentification réussie [admin]
01/10/26 18:10:32.488 : paramètres ['marié'=>oui, 'enfants'=>2, 'salaire'=>55555] valides
01/10/26 18:10:32.489 : données fiscales prises en base de données
01/10/26 18:10:32.491 : {"réponse":{"impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}}
01/10/26 18:10:32.495 : ---nouvelle requête
01/10/26 18:10:32.495 : Authentification en cours…
01/10/26 18:10:32.495 : Authentification réussie [admin]
01/10/26 18:10:32.495 : paramètres ['marié'=>oui, 'enfants'=>2, 'salaire'=>50000] valides
01/10/26 18:10:32.496 : données fiscales prises dans Redis
01/10/26 18:10:32.496 : {"réponse":{"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}}
01/10/26 18:10:32.500 : ---nouvelle requête
01/10/26 18:10:32.500 : Authentification en cours…
01/10/26 18:10:32.500 : Authentification réussie [admin]
01/10/26 18:10:32.500 : paramètres ['marié'=>oui, 'enfants'=>3, 'salaire'=>50000] valides
01/10/26 18:10:32.501 : données fiscales prises dans Redis
01/10/26 18:10:32.501 : {"réponse":{"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}}
…

Nous avons déjà dit qu'avant chaque test, la méthode [setUp] de la classe de test est réexécutée, ce qui fait que la classe [ClientDao] testée est à chaque test instanciée avec un cookie de session inexistant. Tout se passe donc comme si les 11 tests représentaient 11 utilisateurs différents, avec 11 sessions différentes :

  • lignes 2-3, 8-9, 14-15 : chaque test s'authentifie, car il démarre une nouvelle session ;
  • ligne 5 : les données fiscales sont prises en base de données ;
  • lignes 11, 17 : les données fiscales sont prises dans la mémoire de Redis. On a donc bien là une mémoire de portée [application] partagée par tous les utilisateurs de l'application .

20.8. Visualiser le contenu du serveur Redis

Nous avons vu que le serveur Redis pouvait être géré en mode commande. La commande [get taxAdminData] montre la valeur de la clé [taxAdminData] : on reconnaît la chaîne JSON d'un objet de type [TaxAdminData] :

> redis-cli get taxAdminData
"{\"limites\":[9964,27519,73779,156244,0],\"coeffR\":[0,0.14,0.3,0.41,0.45],\"coeffN\":[0,1394.96,5798,13913.69,20163.45],\"plafondQfDemiPart\":1551,\"plafondRevenusCelibatairePourReduction\":21037,\"plafondRevenusCouplePourReduction\":42074,\"valeurReducDemiPart\":3797,\"plafondDecoteCelibataire\":1196,\"plafondDecoteCouple\":1970,\"plafondImpotCouplePourDecote\":2627,\"plafondImpotCelibatairePourDecote\":1595,\"abattementDixPourcentMax\":12502,\"abattementDixPourcentMin\":437}"

[redis-cli] affiche la chaîne entre guillemets, en faisant précéder chacun des guillemets qu'elle contient d'un caractère [\].

Le serveur Redis peut également être géré grâce à une interface graphique. L'outil officiel est [Redis Insight] (https://redis.io/insight), gratuit, disponible pour Windows. Une fois connecté au serveur [127.0.0.1:6379], il affiche la liste des clés mémorisées par le serveur. En cliquant sur la clé [taxAdminData], on voit sa valeur, que l'outil sait afficher sous forme d'objet JSON. On peut aussi y supprimer une clé, en ajouter une autre, ou exécuter les commandes de [redis-cli] dans une console intégrée.