Skip to content

22. Ejercicio práctico – version 11

Todavía es frecuente que los servicios web envíen su respuesta en forma de flujo XML en lugar de un flujo jSON:

  • el flujo jSON es más ligero, pero se necesita un manual de instrucciones para entenderlo;
  • el flujo XML es más prolijo, pero está autodocumentado. Su comprensión es inmediata;

Modificamos el version 11 cliente/servidor para que el servidor envíe ahora un flujo XML como respuesta a sus clients:

Image

22.1. El servidor

Image

Esta arquitectura se implementará mediante los siguientes scripts:

Image

22.1.1. La clase [Utilitaires]

Retomamos la clase [Utilitaires] utilizada a partir de la versión version 03 (véase el párrafo del enlace):


<?php

// espacio de nombres
namespace Application;

// una clase de funciones de utilidad
abstract class Utilitaires {

  public static function cutNewLinechar(string $ligne): string {

  }


  // de https://stackoverflow.com/questions/1397036/how-to-convert-array-to-simplexml
  public static function getXmlForArrayOfAttributes(array $arrayOfAttributes,
    \SimpleXmlElement &$node): void {
    // se analizan los atributos de la tabla
    foreach ($arrayOfAttributes as $attribute => $value) {
      // ¿Es el atributo numérico?
      if (is_numeric($attribute)) {
        // caso del índice de la tabla (pero también otros casos)
        $attribute = 'i' . $attribute;
      }
      // ¿Es $value una matriz?
      if (is_array($value)) {
        // vamos a explorar a su vez la matriz [$value]
        // se añade un nodo al grafo XML
        $subnode = $node->addChild($attribute);
        // llamada recursiva para explorar el array [$value]
        Utilitaires::getXmlForArrayOfAttributes($value, $subnode);
      } else {
        // se añade el nodo al grafo XML
        $node->addChild("$attribute", htmlspecialchars("$value"));
      }
    }
  }
}

Comentarios

  • líneas 14-36: introducimos el método estático [getXmlForArrayOfAttributes] que devuelve la cadena XML de una matriz [arrayOfAttributes] pasada como parámetro. El segundo parámetro es la referencia de un nodo de un grafo XML, de tipo [SimpleXmlElement]. Tras la ejecución, este nodo contiene el grafo XML de la matriz [arrayOfAttributes];

Escribimos la prueba [testXml.php] de la siguiente manera:

Image


<?php

// dependencia
require __DIR__ . "/Utilitaires.php";
// tabla asociativa
$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();

Al ejecutar este script [2], obtenemos lo siguiente en un navegador Chrome:

Image

22.1.2. El script del servidor

El script de servidor [impots-server.php] debe modificarse, así como su archivo de configuración [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"
}

Comentarios

  • la raíz del proyecto es ahora la carpeta de version 11;
  • línea 16: se incluye la nueva clase [Utilitaires];

Los cambios en el script del servidor son los siguientes:


<?php

// cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);

// espacio de nombres
namespace Application;


// preparación de la respuesta JSON del servidor
$response = new Response();
$response->headers->set("content-type", "application/xml");
$response->setCharset("utf-8");

// creación de la capa [métier]
$métier = new ServerMetier($dao);
// cálculo del impuesto
$result = $métier->calculerImpot($marié, (int) $enfants, (int) $salaire);
// se envía la respuesta
sendResponse($response, $result, Response::HTTP_OK, [], $logger, $redis);
// fin
exit;

function doInternalServerError(string $message, Response $response, array $infos,

}

// función de envío de la respuesta HTTP al cliente
function sendResponse(Response $response, array $result, int $statusCode,
  array $headers, Logger $logger = NULL, \Predis\Client $predisClient = NULL) {
  // $response: respuesta HTTP
  // $result: tabla de resultados
  // $statusCode: estado HTTP de la respuesta
  // $headers: encabezados HTTP que deben incluirse en la respuesta
  // $logger: el registrador de la aplicación
  // $predisClient: un cliente [predis]
  //
  // estado HTTTP
  $response->setStatusCode($statusCode);
  // cuerpo XML
  $node = new \SimpleXMLElement("<?xml version='1.0' encoding='UTF-8'?><réponse></réponse>");
  Utilitaires::getXmlForArrayOfAttributes($result, $node);
  $response->setContent($node->asXML());
  // encabezados
  $response->headers->add($headers);
  // envío
  $response->send();
  // registro
  if ($logger != NULL) {
    // registro en jSON
    $log = \json_encode(["réponse" => $result], JSON_UNESCAPED_UNICODE);
    $logger->write("$log\n");
    $logger->close();
  }
  // cierre de la conexión [redis]
  if ($predisClient != NULL) {
    $predisClient->disconnect();
  }
}

Comentarios

  • línea 12: se indica que la respuesta es de tipo [application/xml];
  • líneas 29-59: la respuesta del servidor es ahora XML;
  • línea 41: creación del nodo raíz [<réponse></réponse>] del grafo XML;
  • línea 42: este grafo se completa con el grafo XML de la tabla [$result] de los resultados que se enviarán al cliente;
  • línea 43: el grafo XML se convierte en la cadena XML para su envío al cliente;

Prueba

Directamente en un navegador Chrome, se escribe URL [http://localhost/php7/scripts-web/impots/version-11/impots-server.php?mari%C3%A9=oui&enfants=2&salaire=60000]. Se obtiene el siguiente resultado [1] en un navegador Chrome:

Image

22.2. El cliente

Ahora nos centramos en la parte del cliente de la aplicación.

Image

Esta arquitectura se implementará mediante los siguientes scripts:

Image

En el nuevo version, solo cambian:

  • el archivo de configuración [config-client.json];
  • la capa [dao] del cliente;

El archivo de configuración [config-client.json] queda como sigue:


{
    "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. La capa [dao]

El cliente [ClientDao.php] (línea 13 anterior) se modifica para tener en cuenta el nuevo formato de la respuesta. Se utiliza [simpleXML] para procesarla:


<?php

namespace Application;

// dependencias
use \Symfony\Component\HttpClient\HttpClient;

class ClientDao implements InterfaceClientDao {
  // uso de un Trait
  use TraitDao;
  // atributos
  private $urlServer;
  private $user;
  private $sessionCookie;

  // constructor
  public function __construct(string $urlServer, array $user) {
    $this->urlServer = $urlServer;
    $this->user = $user;
  }

  // cálculo del impuesto
  public function calculerImpot(string $marié, int $enfants, int $salaire): array {

    // se recupera la respuesta XML
    $réponse = $response->getContent(false);
    $xml = new \SimpleXMLElement($réponse);
    // registros
    // imprimir "$réponse\n";
    // se recupera el estado de la respuesta
    $statusCode = $response->getStatusCode();
    // ¿error?
    if ($statusCode !== 200) {
      // hay un error: se lanza una excepción
      $message = \json_encode(["statut HTTP" => $statusCode, "réponse" => $xml], JSON_UNESCAPED_UNICODE);
      throw new ExceptionImpots($message);
    }
    if (!$this->sessionCookie) {
      // se recupera la cookie de sesión
      $headers = $response->getHeaders();
      if (isset($headers["set-cookie"])) {
        // ¿cookie de sesión?
        foreach ($headers["set-cookie"] as $cookie) {
          $match = [];
          $match = preg_match("/^PHPSESSID=(.+?);/", $cookie, $champs);
          if ($match) {
            $this->sessionCookie = "PHPSESSID=" . $champs[1];
          }
        }
      }
    }
    // se devuelve la respuesta en forma de matriz
    return \json_decode(\json_encode($xml, JSON_UNESCAPED_UNICODE), true);
  }

}

Comentarios

  • líneas 26-27: se lee la respuesta del servidor. Se trata de un documento XML [<réponse>…</réponse>]. Se crea un objeto [SimpleXMLElement] a partir del documento XML recibido;
  • líneas 33-37: en caso de error, el mensaje de la excepción será la cadena jSON de la respuesta del servidor en lugar de la cadena XML recopilada. De hecho, la cadena jSON es más concisa;
  • línea 53: se devuelve la tabla de resultados en dos pasos:
    • el objeto [$xml] de tipo [\SimpleXMLElement] se convierte en jSON;
    • se transforma la cadena jSON obtenida en una tabla asociativa. Este es el resultado que se debe devolver;

Prueba

Si se inicia el cliente con un entorno correcto (base de datos, autenticación, registros), se obtienen los resultados habituales (compruebe los archivos [taxpayersdata.json, results.txt, errors.json]). Por parte del servidor, los registros son los siguientes:


06/07/19 07:41:32:877 :
--- nueva solicitud
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 :
---nueva solicitud
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 :
---nueva solicitud
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. Pruebas [Codeception]

Image

La prueba [ClientMetierTest] es la siguiente:


<?php

// Cumplimiento estricto de los tipos declarados de los parámetros de las funciones
declare (strict_types=1);

// espacio de nombres
namespace Application;

// definición de constantes
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-11");

// ruta del archivo de configuración
define("CONFIG_FILENAME", ROOT . "/Data/config-client.json");

// se recupera la configuración
$config = \json_decode(file_get_contents(CONFIG_FILENAME), true);


// clase de prueba
class ClientMetierTest extends Unit {
  // capa de negocio
  private $métier;

  public function __construct() {
    parent::__construct();
    // se recupera la configuración
    $config = \json_decode(\file_get_contents(CONFIG_FILENAME), true);
    // creación de la capa [dao]
    $clientDao = new ClientDao($config["urlServer"], $config["user"]);
    // creación de la capa [métier]
    $this->métier = new ClientMetier($clientDao);
  }

  // pruebas

}

Los resultados de la prueba son los siguientes:

Image