Skip to content

22. Uygulama alıştırması – sürüm 11

Web hizmetlerinin yanıtlarını jSON akışı yerine XML akışı biçiminde göndermesi hâlâ sıkça görülmektedir:

  • jSON akışı daha hafiftir ancak anlaşılması için bir kullanım kılavuzu gerektirir;
  • XML akışı daha ayrıntılıdır ancak kendi kendini açıklar. Anlaşılması hemen mümkündür;

İstemci/sunucu sürüm 11'i, sunucunun artık istemcilere yanıt olarak XML akışını gönderecek şekilde değiştiriyoruz:

Image

22.1. Sunucu

Image

Bu mimari aşağıdaki komut dosyalarıyla uygulanacaktır:

Image

22.1.1. [Utilitaires] sınıfı

03 sürümünden itibaren kullanılan [Utilitaires] sınıfını yeniden kullanıyoruz (bkz. bağlantı paragrafı):


<?php

// ad alanı
namespace Application;

// bir yardımcı işlev sınıfı
abstract class Utilitaires {

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

  }


  // https://stackoverflow.com/questions/1397036/how-to-convert-array-to-simplexml adresinden
  public static function getXmlForArrayOfAttributes(array $arrayOfAttributes,
    \SimpleXmlElement &$node): void {
    // dizinin öznitelikleri taranır
    foreach ($arrayOfAttributes as $attribute => $value) {
      // Özellik sayısal mı?
      if (is_numeric($attribute)) {
        // dizinin indeksi durumu (ancak diğer durumlar da dahil)
        $attribute = 'i' . $attribute;
      }
      // $value bir dizi mi?
      if (is_array($value)) {
        // Şimdi de [$value] dizisini inceleyeceğiz
        // XML grafiğine bir düğüm ekliyoruz
        $subnode = $node->addChild($attribute);
        // [$value] tablosunu incelemek için özyinelemeli çağrı
        Utilitaires::getXmlForArrayOfAttributes($value, $subnode);
      } else {
        // XML grafiğine düğüm eklenir
        $node->addChild("$attribute", htmlspecialchars("$value"));
      }
    }
  }
}

Açıklamalar

  • 14-36. satırlar: Parametre olarak geçirilen [arrayOfAttributes] dizisinden XML dizesini döndüren [getXmlForArrayOfAttributes] statik yöntemini ekliyoruz. İkinci parametre, XML grafiğindeki, [SimpleXmlElement] türündeki bir düğümün referansıdır. Yürütme sonrasında bu düğüm, [arrayOfAttributes] tablosundaki XML grafiğini içerir;

Aşağıdaki [testXml.php] testini yazıyoruz:

Image


<?php

// bağımlılık
require __DIR__ . "/Utilitaires.php";
// ilişkisel tablo
$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();

Bu [2] komut dosyasını çalıştırdığımızda, Chrome tarayıcısında aşağıdakini elde ediyoruz:

Image

22.1.2. Sunucu komut dosyası

[impots-server.php] sunucu komut dosyası ve [config-server.json] yapılandırma dosyası şu şekilde değiştirilmelidir:


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

Notlar

  • projenin kök dizini artık sürüm 11'in klasörüdür;
  • 16. satır: yeni [Utilitaires] sınıfı eklenmiştir;

Sunucu komut dosyasında yapılan değişiklikler şunlardır:


<?php

// fonksiyon parametrelerinin beyan edilen türlerine sıkı uyum
declare (strict_types=1);

// ad alanı
namespace Application;


// sunucudan gelen JSON yanıtının hazırlanması
$response = new Response();
$response->headers->set("content-type", "application/xml");
$response->setCharset("utf-8");

// [métier] katmanının oluşturulması
$métier = new ServerMetier($dao);
// vergi hesaplaması
$result = $métier->calculerImpot($marié, (int) $enfants, (int) $salaire);
// yanıtın gönderilmesi
sendResponse($response, $result, Response::HTTP_OK, [], $logger, $redis);
// son
exit;

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

}

// HTTP yanıtını müşteriye gönderme işlevi
function sendResponse(Response $response, array $result, int $statusCode,
  array $headers, Logger $logger = NULL, \Predis\Client $predisClient = NULL) {
  // $response: HTTP yanıtı
  // $result: sonuç tablosu
  // $statusCode : yanıtın durumu HTTP
  // $headers : yanıtta yer alması gereken başlıklar
  // $logger: uygulamanın günlüğü
  // $predisClient: bir istemci [predis]
  //
  // durum HTTTP
  $response->setStatusCode($statusCode);
  // gövde XML
  $node = new \SimpleXMLElement("<?xml version='1.0' encoding='UTF-8'?><réponse></réponse>");
  Utilitaires::getXmlForArrayOfAttributes($result, $node);
  $response->setContent($node->asXML());
  // başlıklar
  $response->headers->add($headers);
  // gönderim
  $response->send();
  // günlük
  if ($logger != NULL) {
    // günlük kaydı jSON
    $log = \json_encode(["réponse" => $result], JSON_UNESCAPED_UNICODE);
    $logger->write("$log\n");
    $logger->close();
  }
  // bağlantının kapatılması [redis]
  if ($predisClient != NULL) {
    $predisClient->disconnect();
  }
}

Yorumlar

  • 12. satır: yanıtın [application/xml] türü olduğu belirtilir;
  • 29-59. satırlar: Sunucu yanıtı artık XML türündedir;
  • 41. satır: XML grafiğinin [<réponse></réponse>] kök düğümü oluşturulmuştur;
  • satır 42: Bu grafik, müşteriye gönderilecek sonuçlar tablosundaki [$result] tablosuna ait XML grafiği ile tamamlanıyor;
  • 43. satır: XML grafiği, müşteriye gönderilmek üzere XML dizisine dönüştürülür;

Test

Doğrudan bir Chrome tarayıcısında URL [http://localhost/php7/scripts-web/impots/version-11/impots-server.php?mari%C3%A9=oui&enfants=2&salaire=60000] yazıldığında, Chrome tarayıcısında şu sonuç elde edilir: [1]:

Image

22.2. Müşteri

Şimdi uygulamanın müşteri tarafına odaklanalım.

Image

Bu mimari, aşağıdaki komut dosyalarıyla uygulanacaktır:

Image

Yeni sürümde yalnızca şunlar değişmiştir:

  • [config-client.json] yapılandırma dosyası;
  • müşterinin [dao] katmanı;

[config-client.json] yapılandırma dosyası şu şekilde olur:


{
    "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] katmanı

[ClientDao.php] istemcisi (yukarıdaki 13. satır), yeni yanıt biçimini dikkate alacak şekilde değiştirilir. Bu yanıtı işlemek için [simpleXML] kullanılır:


<?php

namespace Application;

// bağımlılıklar
use \Symfony\Component\HttpClient\HttpClient;

class ClientDao implements InterfaceClientDao {
  // Bir Trait kullanımı
  use TraitDao;
  // özellikler
  private $urlServer;
  private $user;
  private $sessionCookie;

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

  // vergi hesaplaması
  public function calculerImpot(string $marié, int $enfants, int $salaire): array {

    // yanıt alınır XML
    $réponse = $response->getContent(false);
    $xml = new \SimpleXMLElement($réponse);
    // günlükler
    // "$réponse\n" yazdır;
    // yanıtın durumunu alıyoruz
    $statusCode = $response->getStatusCode();
    // hata mı?
    if ($statusCode !== 200) {
      // bir hata var - bir istisna atılıyor
      $message = \json_encode(["statut HTTP" => $statusCode, "réponse" => $xml], JSON_UNESCAPED_UNICODE);
      throw new ExceptionImpots($message);
    }
    if (!$this->sessionCookie) {
      // oturum çerezini alıyoruz
      $headers = $response->getHeaders();
      if (isset($headers["set-cookie"])) {
        // oturum çerezi mi?
        foreach ($headers["set-cookie"] as $cookie) {
          $match = [];
          $match = preg_match("/^PHPSESSID=(.+?);/", $cookie, $champs);
          if ($match) {
            $this->sessionCookie = "PHPSESSID=" . $champs[1];
          }
        }
      }
    }
    // yanıtı bir dizi biçiminde döndürüyoruz
    return \json_decode(\json_encode($xml, JSON_UNESCAPED_UNICODE), true);
  }

}

Açıklamalar

  • 26-27. satırlar: Sunucunun yanıtı okunur. Bu, XML [<réponse>…</réponse>] biçiminde bir belgedir. Alınan XML belgesinden bir [SimpleXMLElement] nesnesi oluşturulur;
  • 33-37. satırlar: Hata durumunda, istisna mesajı, toplanan XML dizesi yerine sunucu yanıtındaki jSON dizesi olacaktır. Zira jSON dizesi daha kısadır;
  • 53. satır: Sonuç tablosu iki aşamada döndürülür:
    • [\SimpleXMLElement] türündeki [$xml] nesnesi, jSON'e dönüştürülür;
    • elde edilen jSON dizesi, ilişkisel bir tabloya dönüştürülür. Bu, döndürülecek sonuçtur;

Test

İstemciyi doğru bir ortamda (veritabanı, kimlik doğrulama, günlükler) çalıştırırsak, her zamanki sonuçları elde ederiz ([taxpayersdata.json, results.txt, errors.json] dosyalarını kontrol edin). Sunucu tarafında ise günlükler şu şekildedir:


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] Testleri

Image

[ClientMetierTest] testi şu şekildedir:


<?php

// fonksiyon parametrelerinin beyan edilen türlerine sıkı sıkıya uyulur
declare (strict_types=1);

// ad alanı
namespace Application;

// sabitlerin tanımlanması
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-11");

// yapılandırma dosyasının yolu
define("CONFIG_FILENAME", ROOT . "/Data/config-client.json");

// yapılandırmayı alıyoruz
$config = \json_decode(file_get_contents(CONFIG_FILENAME), true);


// test sınıfı
class ClientMetierTest extends Unit {
  // iş mantığı katmanı
  private $métier;

  public function __construct() {
    parent::__construct();
    // yapılandırmayı alıyoruz
    $config = \json_decode(\file_get_contents(CONFIG_FILENAME), true);
    // katman oluşturma [dao]
    $clientDao = new ClientDao($config["urlServer"], $config["user"]);
    // katman oluşturma [métier]
    $this->métier = new ClientMetier($clientDao);
  }

  // testler

}

Test sonuçları şöyledir:

Image