Skip to content

22. Exercício de Aplicação – Versão 11

Ainda é comum que os serviços web enviem a sua resposta como um feed XML em vez de um feed JSON:

  • o fluxo JSON é mais leve, mas é necessário um guia do utilizador para o compreender;
  • o feed XML é mais detalhado, mas é autoexplicativo. É imediatamente compreensível;

Modificamos a versão 11 do cliente/servidor para que o servidor envie agora um feed XML como resposta aos seus clientes:

Image

22.1. O servidor

Image

Esta arquitetura será implementada pelos seguintes scripts:

Image

22.1.1. A classe [Utilities]

Estamos a reutilizar a classe [Utilities] utilizada desde a versão 03 (ver parágrafo em link):


<?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"));
      }
    }
  }
}

Comentários

  • linhas 14–36: introduzimos o método estático [getXmlForArrayOfAttributes], que devolve a cadeia XML de um array [arrayOfAttributes] passado como parâmetro. O segundo parâmetro é a referência a um nó do gráfico XML do tipo [SimpleXmlElement]. Após a execução, este nó contém a árvore XML do array [arrayOfAttributes];

Escrevemos o seguinte teste [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();

Quando executamos este script [2], obtemos o seguinte num navegador Chrome:

Image

22.1.2. O script do servidor

O script do servidor [impots-server.php] deve ser modificado, assim como o seu ficheiro de configuração [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"
}

Comentários

  • A raiz do projeto é agora a pasta da versão 11;
  • linha 16: a nova classe [Utilities] foi incluída;

As alterações ao script do servidor são as seguintes:


<?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();
  }
}

Comentários

  • linha 12: especifica que a resposta é do tipo [application/xml];
  • linhas 29–59: a resposta do servidor é agora XML;
  • linha 41: criação do nó raiz [<response></response>] do gráfico XML;
  • linha 42: esta árvore é preenchida com a árvore XML da matriz [$result] de resultados a enviar para o cliente;
  • linha 43: a árvore XML é convertida numa cadeia de caracteres XML para envio ao cliente;

Teste

Diretamente num navegador Chrome, introduza o URL [http://localhost/php7/scripts-web/impots/version-11/impots-server.php?mari%C3%A9=oui&enfants=2&salaire=60000]. O seguinte resultado [1] é apresentado num navegador Chrome:

Image

22.2. O cliente

Vamos agora concentrar-nos no lado do cliente da aplicação.

Image

Esta arquitetura será implementada pelos seguintes scripts:

Image

Na nova versão, as únicas alterações são:

  • o ficheiro de configuração [config-client.json];
  • a camada [dao] do cliente;

O ficheiro de configuração [config-client.json] passa a ter o seguinte conteúdo:


{
    "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. A camada [dao]

O cliente [ClientDao.php] (linha 13 acima) é modificado para ter em conta o novo formato de resposta. Utilizamos [simpleXML] para o processar:


<?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 given in the form of a table
    return \json_decode(\json_encode($xml, JSON_UNESCAPED_UNICODE), true);
  }
 
}

Comentários

  • linhas 26-27: a resposta do servidor é lida. Trata-se de um documento XML [<response>…</response>]. Um objeto [SimpleXMLElement] é construído a partir do documento XML recebido;
  • linhas 33-37: em caso de erro, a mensagem de exceção será a cadeia JSON da resposta do servidor, em vez da cadeia XML. Isto porque a cadeia JSON é mais concisa;
  • linha 53: a matriz de resultados é devolvida em duas etapas:
    • o objeto [$xml] do tipo [\SimpleXMLElement] é convertido para JSON;
    • Convertemos a cadeia JSON resultante numa matriz associativa. Este é o resultado a ser devolvido;

Teste

Se executarmos o cliente num ambiente adequado (base de dados, autenticação, registos), obtemos os resultados habituais (verifique os ficheiros [taxpayersdata.json, results.txt, errors.json]). No lado do servidor, os registos são os seguintes:


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. Testes [Codeception]

Image

O teste [ClientMetierTest] é o seguinte:


<?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

}

Os resultados dos testes são os seguintes:

Image