Skip to content

22. Application Exercise – version 11

It is still common for web services to send their response in the form of a XML stream rather than a jSON stream:

  • the jSON stream is lighter but requires a user guide to understand it;
  • the XML stream is more verbose but is self-documenting. It is immediately understandable;

We are modifying the version client/server so that the server now sends a XML stream in response to its clients:

Image

22.1. The server

Image

This architecture will be implemented by the following scripts:

Image

22.1.1. The [Utilitaires] class

We reuse the [Utilitaires] class used starting with version 03 (see link paragraph):


<?php
 
// namespace
namespace Application;
 
// a class of utility functions
abstract class Utilitaires {
 
  public static function cutNewLinechar(string $ligne): string {

  }
 
 
  // from https://stackoverflow.com/questions/1397036/how-to-convert-array-to-simplexml
  public static function getXmlForArrayOfAttributes(array $arrayOfAttributes,
    \SimpleXmlElement &$node): void {
    // scan array attributes
    foreach ($arrayOfAttributes as $attribute => $value) {
      // is the attribute numeric?
      if (is_numeric($attribute)) {
        // table index case (but also other cases)
        $attribute = 'i' . $attribute;
      }
      // is $value an array?
      if (is_array($value)) {
        // we'll explore the [$value] array in turn
        // we add a node to the XML graph
        $subnode = $node->addChild($attribute);
        // recursive call to explore the [$value] array
        Utilitaires::getXmlForArrayOfAttributes($value, $subnode);
      } else {
        // we add the node to the XML graph
        $node->addChild("$attribute", htmlspecialchars("$value"));
      }
    }
  }
}

Comments

  1. lines 14–36: we introduce the static method [getXmlForArrayOfAttributes], which returns the string XML from an array [arrayOfAttributes] passed as a parameter. The second parameter is the reference to a node in a graph XML, of type [SimpleXmlElement]. After execution, this node contains the graph XML from the array [arrayOfAttributes];

We write the following test [testXml.php]:

Image


<?php
 
// dependency
require __DIR__ . "/Utilitaires.php";
// associative table
$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();

When we run this script [2], we get the following in a Chrome browser:

Image

22.1.2. The server script

The server script [impots-server.php] must be modified, along with its configuration file [config-server.json]:


{
    "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"
}

Comments

  1. The project root is now the version 11 folder;
  2. line 16: the new [Utilitaires] class is included;

The changes to the server script are as follows:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 

// prepare JSON server response
$response = new Response();
$response->headers->set("content-type", "application/xml");
$response->setCharset("utf-8");

// creation of the [business] layer
$métier = new ServerMetier($dao);
// tAX CALCULATION
$result = $métier->calculerImpot($marié, (int) $enfants, (int) $salaire);
// we return the answer
sendResponse($response, $result, Response::HTTP_OK, [], $logger, $redis);
// end
exit;
 
function doInternalServerError(string $message, Response $response, array $infos,

}
 
// function to send HTTP response to client
function sendResponse(Response $response, array $result, int $statusCode,
  array $headers, Logger $logger = NULL, \Predis\Client $predisClient = NULL) {
  // $response : answer HTTP
  // $result: results table
  // $statusCode: HTTP response status
  // $headers: HTTP headers to be included in the response
  // $logger: application logger
  // $predisClient: a customer [predis]
  //
  // status HTTTP
  $response->setStatusCode($statusCode);
  // body XML
  $node = new \SimpleXMLElement("<?xml version='1.0' encoding='UTF-8'?><réponse></réponse>");
  Utilitaires::getXmlForArrayOfAttributes($result, $node);
  $response->setContent($node->asXML());
  // headers
  $response->headers->add($headers);
  // shipping
  $response->send();
  // log
  if ($logger != NULL) {
    // log in jSON
    $log = \json_encode(["réponse" => $result], JSON_UNESCAPED_UNICODE);
    $logger->write("$log\n");
    $logger->close();
  }
  // close connection [redis]
  if ($predisClient != NULL) {
    $predisClient->disconnect();
  }
}

Comments

  1. line 12: the response type is specified as [application/xml];
  2. lines 29–59: the server’s response is now of type XML;
  3. line 41: creation of the root node [<réponse></réponse>] of the graph XML;
  4. line 42: this graph is supplemented with the graph XML from the table [$result] of results to be sent to the client;
  5. line 43: the graph XML is converted to the string XML for sending to the client;

Test

Directly in a Chrome browser, enter URL [http://localhost/php7/scripts-web/impots/version-11/impots-server.php?mari%C3%A9=oui&enfants=2&salaire=60000]. The following result, [1], is obtained in a Chrome browser:

Image

22.2. The client

We will now focus on the client-side of the application.

Image

This architecture will be implemented by the following scripts:

Image

In the new version, the only changes are:

  • the configuration file [config-client.json];
  • the client layer [dao];

The configuration file [config-client.json] becomes the following:


{
    "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. The [dao] layer

The [ClientDao.php] client (line 13 above) is modified to account for the new response format. [simpleXML] is used to process it:


<?php
 
namespace Application;
 
// dependencies
use \Symfony\Component\HttpClient\HttpClient;
 
class ClientDao implements InterfaceClientDao {
  // using a Trait
  use TraitDao;
  // attributes
  private $urlServer;
  private $user;
  private $sessionCookie;
 
  // manufacturer
  public function __construct(string $urlServer, array $user) {
    $this->urlServer = $urlServer;
    $this->user = $user;
  }
 
  // tAX CALCULATION
  public function calculerImpot(string $marié, int $enfants, int $salaire): array {

    // we retrieve the XML response
    $réponse = $response->getContent(false);
    $xml = new \SimpleXMLElement($réponse);
    // logs
    // print "$réponse";
    // retrieve response status
    $statusCode = $response->getStatusCode();
    // mistake?
    if ($statusCode !== 200) {
      // we have an error - we throw an exception
      $message = \json_encode(["statut HTTP" => $statusCode, "réponse" => $xml], JSON_UNESCAPED_UNICODE);
      throw new ExceptionImpots($message);
    }
    if (!$this->sessionCookie) {
      // retrieve the session cookie
      $headers = $response->getHeaders();
      if (isset($headers["set-cookie"])) {
        // session cookie ?
        foreach ($headers["set-cookie"] as $cookie) {
          $match = [];
          $match = preg_match("/^PHPSESSID=(.+?);/", $cookie, $champs);
          if ($match) {
            $this->sessionCookie = "PHPSESSID=" . $champs[1];
          }
        }
      }
    }
    // the answer is returned in the form of a table
    return \json_decode(\json_encode($xml, JSON_UNESCAPED_UNICODE), true);
  }
 
}

Comments

  • lines 26–27: The server response is read. It is a XML [<réponse>…</réponse>] document. A [SimpleXMLElement] object is constructed from the received XML document;
  • lines 33–37: in case of an error, the exception message will be the string jSON from the server response rather than the string XML collected. This is because the string jSON is more concise;
  • line 53: the results array is returned in two steps:
    • the object [$xml] of type [\SimpleXMLElement] is converted to jSON;
    • the resulting string jSON is converted into an associative array. This is the result to be returned;

Test

If you run the client in a proper environment (database, authentication, logs), you get the usual results (check the [taxpayersdata.json, results.txt, errors.json] files). On the server side, the logs are as follows:


06/07/19 07:41:32:877 :
---new request
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 ['married'=>yes, 'children'=>2, 'salary'=>55555] valid
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 :
---new request
06/07/19 07:41:33:077 : Authentification prise en session…
06/07/19 07:41:33:077 : paramètres ['married'=>yes, 'children'=>2, 'salary'=>50000] valid
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 :
---new request
06/07/19 07:41:33:202 : Authentification prise en session…
06/07/19 07:41:33:202 : paramètres ['married'=>yes, 'children'=>3, 'salary'=>50000] valid
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]

Image

The [ClientMetierTest] test is as follows:


<?php
 
// strict adherence to declared types of function parameters
declare (strict_types=1);
 
// namespace
namespace Application;
 
// definition of constants
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-11");
 
// configuration file path
define("CONFIG_FILENAME", ROOT . "/Data/config-client.json");
 
// we retrieve the configuration
$config = \json_decode(file_get_contents(CONFIG_FILENAME), true);
 

// test class
class ClientMetierTest extends Unit {
  // business layer
  private $métier;
 
  public function __construct() {
    parent::__construct();
    // we retrieve the configuration
    $config = \json_decode(\file_get_contents(CONFIG_FILENAME), true);
    // creation of the [dao] layer
    $clientDao = new ClientDao($config["urlServer"], $config["user"]);
    // creation of the [business] layer
    $this->métier = new ClientMetier($clientDao);
  }
 
  // tests

}

The test results are as follows:

Image