Skip to content

22. تمرين التطبيق – الإصدار 11

لا يزال من الشائع أن ترسل خدمات الويب استجاباتها كخلاصة XML بدلاً من خلاصة JSON:

  • تدفق JSON أخف، لكنك تحتاج إلى دليل مستخدم لفهمه؛
  • تغذية XML أكثر تفصيلاً ولكنها توثيق ذاتي. وهي مفهومة على الفور؛

نقوم بتعديل إصدار العميل/الخادم 11 بحيث يرسل الخادم الآن موجز XML كرد على عملائه:

Image

22.1. الخادم

Image

سيتم تنفيذ هذه البنية بواسطة البرامج النصية التالية:

Image

22.1.1. فئة [Utilities]

نحن نعيد استخدام فئة [Utilities] المستخدمة منذ الإصدار 03 (انظر الفقرة المرتبطة):


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

تعليقات

  • الأسطر 14–36: نقدم الطريقة الثابتة [getXmlForArrayOfAttributesالتي تُرجع سلسلة XML لمصفوفة [arrayOfAttributes] تم تمريرها كمعلمة. المعلمة الثانية هي مرجع إلى عقدة مخطط XML من النوع [SimpleXmlElement]. بعد التنفيذ، تحتوي هذه العقدة على شجرة XML لمصفوفة [arrayOfAttributes]؛

نكتب الاختبار التالي [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();

عند تشغيل هذا البرنامج النصي [2]، نحصل على النتيجة التالية في متصفح Chrome:

Image

22.1.2. نص برمجي الخادم

يجب تعديل البرنامج النصي للخادم [impots-server.php]، وكذلك ملف التكوين الخاص به [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"
}

تعليقات

  • أصبح جذر المشروع الآن هو مجلد الإصدار 11؛
  • السطر 16: تم تضمين فئة [Utilities] الجديدة؛

التغييرات التي طرأت على البرنامج النصي للخادم هي كما يلي:


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

التعليقات

  • السطر 12: يحدد أن الاستجابة من النوع [application/xml]؛
  • الأسطر 29–59: أصبحت استجابة الخادم الآن بتنسيق XML؛
  • السطر 41: إنشاء العقدة الجذرية [<response></response>] لمخطط XML؛
  • السطر 42: يتم ملء هذه الشجرة بشجرة XML من مصفوفة [$result] للنتائج التي سيتم إرسالها إلى العميل؛
  • السطر 43: يتم تحويل شجرة XML إلى سلسلة XML لإرسالها إلى العميل؛

اختبار

أدخل عنوان URL [http://localhost/php7/scripts-web/impots/version-11/impots-server.php?mari%C3%A9=oui&enfants=2&salaire=60000] مباشرة في متصفح Chrome. يتم عرض النتيجة التالية [1] في متصفح Chrome:

Image

22.2. العميل

سنركز الآن على جانب العميل من التطبيق.

Image

سيتم تنفيذ هذه البنية من خلال البرامج النصية التالية:

Image

في الإصدار الجديد، التغييرات الوحيدة هي:

  • ملف التكوين [config-client.json]؛
  • طبقة [dao] الخاصة بالعميل؛

يصبح ملف التكوين [config-client.json] كما يلي:


{
    "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. طبقة [dao]

تم تعديل العميل [ClientDao.php] (السطر 13 أعلاه) لمراعاة تنسيق الاستجابة الجديد. نستخدم [simpleXML] لمعالجته:


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

تعليقات

  • السطران 26-27: تتم قراءة استجابة الخادم. وهي عبارة عن مستند XML [<response>…</response>]. يتم إنشاء كائن [SimpleXMLElement] من مستند XML المستلم؛
  • السطور 33-37: في حالة حدوث خطأ، ستكون رسالة الاستثناء عبارة عن سلسلة JSON لاستجابة الخادم بدلاً من سلسلة XML. وذلك لأن سلسلة JSON أكثر إيجازًا؛
  • السطر 53: يتم إرجاع مصفوفة النتائج على خطوتين:
    • يتم تحويل الكائن [$xml] من النوع [\SimpleXMLElement] إلى JSON؛
    • نقوم بتحويل سلسلة JSON الناتجة إلى مصفوفة ترابطية. هذه هي النتيجة التي سيتم إرجاعها؛

اختبار

إذا قمنا بتشغيل العميل في بيئة مناسبة (قاعدة بيانات، مصادقة، سجلات)، نحصل على النتائج المعتادة (تحقق من الملفات [taxpayersdata.json، results.txt، errors.json]). على جانب الخادم، تكون السجلات كما يلي:


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. الاختبارات [Codeception]

Image

اختبار [ClientMetierTest] هو كما يلي:


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

}

نتائج الاختبار هي كما يلي:

Image