Skip to content

18. Uygulama alıştırması – sürüm 8

Örnek uygulamayı – sürüm 5’i (bağlantı paragrafı) ele alacağız ve bunu bir istemci/sunucu uygulaması haline getireceğiz.

18.1. Introduction

Sürüm 5'in mimarisi şöyleydi:

Image

  • [dao] (Veri Erişim Nesneleri) adlı katman, MySQL veritabanı ve yerel dosya sistemi ile veri alışverişini yürütür;
  • [métier] adlı katman vergi hesaplamasını yapar;
  • ana komut dosyası, tüm süreci yöneten orkestra şefi gibidir: [dao] ve [métier] katmanlarını başlatır, ardından yapılması gereken işlemleri gerçekleştirmek üzere [métier] katmanıyla iletişim kurar;

Bu mimariyi aşağıdaki istemci/sunucu mimarisine geçireceğiz:

Image

  • [2]'te, sürüm 5'teki [dao] katmanını, yerel dosya sistemine erişim yöntemlerini kaldırarak devralacağız. Bu yöntemler, [6, 7] istemcisinin [dao] katmanına taşınacaktır;
  • [3]'te, [métier] katmanı, [executeBatchImpôts, saveResults] yöntemleri hariç olmak üzere sürüm 5'teki haliyle kalacak; bu yöntemler, istemcinin [dao] ve [7] katmanlarına taşınacaktır;
  • [4]'te sunucu komut dosyası yazılmalıdır: bu komut dosyası şunları yapmalıdır:
    • [métier], [dao] ve [3, 2] katmanlarını oluşturmak;
    • [5, 7] istemci komut dosyasıyla iletişim kurmak;
  • [7]'te, istemcinin [dao] katmanı yazılmalıdır:
    • bu, [4, 5] sunucu betiğinin bir HTTP istemcisi olacaktır;
    • bu katman, sürüm 5’teki [dao] katmanının yerel dosya sistemine erişim yöntemlerini devralacaktır;
  • [8]'te, istemcinin [métier] katmanı, sürüm 5'teki [InterfaceMetier] arayüzüne uyacaktır. Ancak bunun uygulaması farklı olacaktır. Sürüm 5'te, [métier] katmanı vergi hesaplamasını yapıyordu. Burada ise bu hesaplamayı sunucunun [métier] katmanı yapmaktadır. Dolayısıyla [métier] katmanı, sunucuyla iletişim kurmak ve vergiyi hesaplamasını istemek için [dao] ve [7] katmanlarını çağıracaktır;
  • [9] katmanında ise konsol komut dosyası, istemcinin [dao, métier] katmanını başlatacak ve bunun yürütülmesini sağlayacaktır;

18.2. Sunucu

Uygulamanın sunucu tarafıyla ilgileniyoruz.

Image

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

Image

18.2.1. Katmanlar arasında alışverişi yapılan varlıklar

Image

Katmanlar arasında alışverişi yapılan varlıklar, bağlantı paragrafında açıklanan sürüm 5'e ait varlıklardır.

18.2.2. [dao] katmanı

Image

[dao] katmanı, aşağıdaki [InterfaceServerDao] arayüzünü uygular:


<?php

// ad alanı
namespace Application;

interface InterfaceServerDao {

  // Vergi idaresi verilerinin okunması
  public function getTaxAdminData(): TaxAdminData;
}
  • 9. satır: [getTaxAdminData] yöntemi, vergi idaresine ait verileri bir veritabanından alır;

[InterfaceServerDao] arayüzü, aşağıdaki [ServerDao] sınıfı tarafından uygulanmaktadır:


<?php

// ad alanı
namespace Application;

// ImpotsWithDataInDatabase sınıfının tanımlanması
class ServerDao implements InterfaceServerDao {
  // vergi dilimlerine ait verileri içeren TaxAdminData türündeki nesne
  private $taxAdminData;
  // [Database] türündeki ve BD'in özelliklerini içeren nesne
  private $database;

  // üretici
  public function __construct(string $databaseFilename) {
    // veritabanının JSON yapılandırması kaydedilir
    $this->database = (new Database())->setFromJsonFile($databaseFilename);
    // özelliği hazırlanıyor
    $this->taxAdminData = new TaxAdminData();
    try {
      // veritabanına bağlantı açılıyor
      $connexion = new \PDO($this->database->getDsn(), $this->database->getId(), $this->database->getPwd());
      // SGBD'te her hata durumunda bir istisna atılmasını istiyoruz
      $connexion->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
      // bir işlem başlatıyoruz
      $connexion->beginTransaction();
      // vergi dilimleri tablosu doldurulur
      $this->getTranches($connexion);
      // sabitler tablosu doldurulur
      $this->getConstantes($connexion);
      // işlem başarıyla sonlandırılır
      $connexion->commit();
    } catch (\PDOException $ex) {
      // devam eden bir işlem var mı?
      if (isset($connexion) && $connexion->inTransaction()) {
        // işlem başarısızlıkla sonlandırılıyor
        $connexion->rollBack();
      }
      // istisna, çağıran koda iletilir
      throw new ExceptionImpots($ex->getMessage());
    } finally {
      // bağlantı kapatılıyor
      $connexion = NULL;
    }
  }

  // veritabanından veriler okunuyor
  private function getTranches($connexion): void {

  }

  // sabitler tablosundan okuma
  private function getConstantes($connexion): void {

  }

  // vergi hesaplamasına olanak tanıyan verileri döndürür
  public function getTaxAdminData(): TaxAdminData {
    return $this->taxAdminData;
  }

}

Bu kod, bağlantı paragrafında sunulmuştur.

18.2.3. [métier] katmanı

Image

Image

[métier] katmanı, aşağıdaki [InterfaceServerMetier] arayüzünü uygular:


<?php

// ad alanı
namespace Application;

interface InterfaceServerMetier {

  // bir vergi mükellefinin vergilerinin hesaplanması
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;
}

[InterfaceServerMetier] arayüzü, aşağıdaki [ServerMetier] sınıfı tarafından uygulanmaktadır:


<?php

// ad alanı
namespace Application;

class ServerMetier implements InterfaceServerMetier {
  // Dao katmanı
  private $dao;
  // vergi idaresi verileri
  private $taxAdminData;

  //---------------------------------------------
  // [dao] katman ayarlayıcısı
  public function setDao(InterfaceServerDao $dao) {
    $this->dao = $dao;
    return $this;
  }

  public function __construct(InterfaceServerDao $dao) {
    // [dao] katmanında bir referans kaydedilir
    $this->dao = $dao;
    // vergi hesaplamasına olanak tanıyan veriler alınır
    // [getTaxAdminData] yöntemi bir istisna oluşturabilir ExceptionImpots
    // bu durumda istisna, çağıran koda iletilir
    $this->taxAdminData = $this->dao->getTaxAdminData();
  }

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

    // sonuç
    return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
  }

// --------------------------------------------------------------------------
  private function calculerImpot2(string $marié, int $enfants, float $salaire): array {

    // sonuç
    return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
  }

  // revenuImposable=yıllıkMaaş-muafiyet
  // muafiyetin bir minimum ve bir maksimum değeri vardır
  private function getRevenuImposable(float $salaire): float {

    // sonuç
    return floor($revenuImposable);
  }

// olası bir indirimi hesaplar
  private function getDecôte(string $marié, float $salaire, float $impots): float {

    // sonuç
    return ceil($décôte);
  }

// olası bir indirimi hesaplar
  private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
    ..
    // sonuç
    return ceil($réduction);
  }
}

Bu kod, 1. sürümden itibaren bağlantı başlığı altında daha önce ele alınmış ve yorumlanmıştır. Veritabanına sahip nesne tabanlı sürümü ise bağlantı başlığı altında sunulmuştur.

18.2.4. Sunucu komut dosyası

Image

Image

Sunucu betiği, [web] [4] katmanını uygular. [impots-server] betiği, aşağıdaki jSON [config-server.json] dosyası ile yapılandırılır:


{
    "rootDirectory": "C:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-08",
    "databaseFilename": "Data/database.json",
    "taxAdminDataFileName": "Data/taxadmindata.json",
    "relativeDependencies": [
        "/Entities/BaseEntity.php",
        "/Entities/ExceptionImpots.php",
        "/Entities/TaxAdminData.php",
        "/Entities/Database.php",
        "/Dao/InterfaceServerDao.php",
        "/Dao/ServerDao.php",
        "/Métier/InterfaceServerMetier.php",
        "/Métier/ServerMetier.php"
    ],
    "absoluteDependencies": ["C:/myprograms/laragon-lite/www/vendor/autoload.php"],
    "users": [
        {
            "login": "admin",
            "passwd": "admin"
        }
    ]
}
  • 1. satır: dosya yollarının hesaplanacağı kök dizin;
  • 2. satır: MySQL veritabanının yapılandırma dosyası olan jSON;
  • 3. satır: vergi idaresi verilerini içeren jSON dosyası;
  • satır 5-14: uygulama dosyaları;
  • 15. satır: üçüncü taraf kütüphanelere (burada Symfony) gerekli bağımlılık;
  • satır 16-20: uygulamayı kullanmaya yetkili kullanıcıların tablosu;

jSON ve [database.json, taxadmindata.json] dosyaları, bağlantı paragrafında açıklanan sürüm 5'e aittir.

[impots-server] betiği, [web] katmanını şu şekilde uygular:


<?php

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

// ad alanı
namespace Application;

// PHP ile hata yönetimi
//ini_set("display_errors", "0");
//
// yapılandırma dosyasının yolu
define("CONFIG_FILENAME", "Data/config-server.json");

// yapılandırma alınır
$config = \json_decode(file_get_contents(CONFIG_FILENAME), true);

// komut dosyası için gerekli bağımlılıklar ekleniyor
$rootDirectory = $config["rootDirectory"];
foreach ($config["relativeDependencies"] as $dependency) {
  require "$rootDirectory$dependency";
}
// mutlak bağımlılıklar (üçüncü taraf kütüphaneler)
foreach ($config["absoluteDependencies"] as $dependency) {
  require "$dependency";
}

// sabitlerin tanımlanması
define("DATABASE_CONFIG_FILENAME", $config["databaseFilename"]);
//
// Symfony bağımlılıkları
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;

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

// geçerli isteği alıyoruz
$request = Request::createFromGlobals();
// kimlik doğrulama
$requestUser = $request->headers->get('php-auth-user');
$requestPassword = $request->headers->get('php-auth-pw');
// kullanıcı mevcut mu?
$users = $config["users"];
$i = 0;
$trouvé = FALSE;
while (!$trouvé && $i < count($users)) {
  $trouvé = ($requestUser === $users[$i]["login"] && $users[$i]["passwd"] === $requestPassword);
  $i++;
}
// yanıtın durum kodunun belirlenmesi
if (!$trouvé) {
  // bulunamadı - kod 401
  $response->setStatusCode(Response::HTTP_UNAUTHORIZED);
  $response->headers->add(["WWW-Authenticate" => "Basic realm=" . utf8_decode("\"Serveur de calcul d'impôts\"")]);
  // hata mesajı
  $response->setContent(\json_encode(["réponse" => ["erreur" => "Echec de l'authentification [$requestUser, $requestPassword]"]], JSON_UNESCAPED_UNICODE));
  $response->send();
  // son
  exit;
}
// geçerli bir kullanıcı var - alınan parametreler kontrol ediliyor
$erreurs = [];
// üç parametre olması gerekiyor GET
$method = strtolower($request->getMethod());
$erreur = $method !== "get" || $request->query->count() != 3;
// hata mı?
if ($erreur) {
  $erreurs[] = "Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]";
}

// medeni durumu alınıyor
if (!$request->query->has("marié")) {
  $erreurs[] = "paramètre marié manquant";
} else {
  $marié = trim(strtolower($request->query->get("marié")));
  $erreur = $marié !== "oui" && $marié !== "non";
  // hata mı?
  if ($erreur) {
    $erreurs[] = "paramètre marié [$marié] invalide";
  }
}

// çocuk sayısını alıyoruz
if (!$request->query->has("enfants")) {
  $erreurs[] = "paramètre enfants manquant";
} else {
  $enfants = trim($request->query->get("enfants"));
  // çocuk sayısı 0'dan büyük bir tamsayı olmalıdır
  $erreur = !preg_match("/^\d+$/", $enfants);
  // hata mı?
  if ($erreur) {
    $erreurs[] = "paramètre enfants [$enfants] invalide";
  }
}

// yıllık maaş alınır
if (!$request->query->has("salaire")) {
  $erreurs[] = "paramètre salaire manquant";
} else {
  // maaş, 0'dan büyük veya 0'a eşit bir tamsayı olmalıdır
  $salaire = trim($request->query->get("salaire"));
  $erreur = !preg_match("/^\d+$/", $salaire);
  // hata mı?
  if ($erreur) {
    $erreurs[] = "paramètre salaire [$salaire] invalide";
  }
}

// istek içinde başka parametreler mi var?
foreach (\array_keys($request->query->all()) as $key) {
  // parametre geçerli mi?
  if (!\in_array($key, ["marié", "enfants", "salaire"])) {
    $erreurs[] = "paramètre [$key] invalide";}
}

// hatalar mı?
if ($erreurs) {
  // istemciye 400 hata kodu gönderiliyor
  $response->setStatusCode(Response::HTTP_BAD_REQUEST);
  $response->setContent(json_encode(["réponse" => ["erreurs" => $erreurs]], JSON_UNESCAPED_UNICODE));
  $response->send();
  exit;
}
// Çalışmak için gerekli her şey mevcut
// sunucu mimarisinin oluşturulması
$msgErreur = "";
try {
  // katman oluşturma [dao]
  $dao = new ServerDao($config["databaseFilename"]);
  // katmanın oluşturulması [métier]
  $métier = new ServerMetier($dao);
} catch (ExceptionImpots $ex) {
// hata kaydedildi
  $msgErreur = utf8_encode($ex->getMessage());
}
// hata mı?
if ($msgErreur) {
  // müşteriye 500 hata kodu gönderiliyor
  $response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
  $response->setContent(\json_encode(["réponse" => ["erreur" => $msgErreur]], JSON_UNESCAPED_UNICODE));
  $response->send();
  exit;
}
// vergi hesaplaması
$result = $métier->calculerImpot($marié, (int) $enfants, (int) $salaire);
// yanıt veriliyor
$response->setContent(json_encode(["réponse" => $result], JSON_UNESCAPED_UNICODE));
$response->send();

Açıklamalar

  • 16. satır: yapılandırma dosyası kullanılır;
  • 18-26. satırlar: tüm bağımlılıklar yüklenir;
  • 29. satır: [database.json] dosyasının adı;
  • 32-33. satırlar: kullanılacak üçüncü taraf kütüphanelerin sınıfları tanımlanır;
  • 36-38. satırlar: jSON yanıtı hazırlanıyor;
  • 40-52. satırlar: İsteği yapan kullanıcının yetkili kullanıcılar arasında olup olmadığı kontrol edilir;
  • 54-63. satırlar: Aksi takdirde, erişimin reddedildiğini belirten HTTP 401 kodunu gönderilir. Bu kod ve HTTP [WWW-Authenticate => Basic realm=] başlığı alındığında, çoğu tarayıcı kullanıcıyı kimlik doğrulaması yapmaya davet eden bir kimlik doğrulama penceresi görüntüler;
  • 59. satır: Sunucudan gelen jSON yanıtı, hatanın nedenini açıklar. Sunucudan gelen tüm yanıtlar, [‘réponse’=>’qq chose’] tablosundaki jSON dizesi olacaktır;
  • 64-117. satırlar: İsteğin geçerliliği kontrol edilir:
    • tam olarak üç parametre içeren bir GET isteği;
    • değeri ‘evet’ veya ‘hayır’ olması gereken bir [marié] parametresi;
    • değeri >=0 olan bir tamsayı olması gereken [enfants] parametresi;
    • değeri 0'dan büyük bir tamsayı olması gereken [salaire] parametresi;
  • 65. satır: Her hata tespit edildiğinde, [$erreurs] tablosuna bir hata mesajı eklenir;
  • satır 120-126: hata varsa, HTTP [400 Bad Request] kodu müşteriye gönderilir (satır 122);
  • satır 123: Sunucudan gelen jSON yanıtı, hatanın nedenini açıklar;
  • 132. satırdan itibaren her şey kontrol edildi. [dao, métier] katmanlarını örneklendirebiliriz. Bu örneklendirme bir maliyete sahiptir ve yalnızca geçerli bir istek olduğundan emin olunduğunda yapılmalıdır;
  • 130-138. satırlar: Sunucu mimarisi oluşturulur. [dao] katmanının oluşturulması sırasında [ExceptionImpots] türünde bir istisna ortaya çıkabilir. Bu istisna meydana gelirse, hata kaydedilir;
  • 135-138. satırlar: Bir istisna meydana gelmişse, istemciye HTTP 500 kodu gönderilir. Bu kod, sunucuda bir hata oluştuğunu belirtir;
  • 143. satır: Yanıt, hatanın nedenini açıklar;
  • 148. satır : vergi hesaplaması [métier] katmanına devredilir;
  • 150-151. satırlar: yanıtın gönderilmesi;

Bu komut dosyasını bir tarayıcıyla test edelim. Güvenli URL'i [https://localhost:443/php7/scripts-web/impots/version-08/impots-server.php?marié=oui&enfants=5&salaire=100000]'ten isteyelim:

Image

  • [1] olarak, istenen güvenli URL;
  • [2]'e, üç parametre [marié, enfants, salaire];
  • [3]'te, Laragon'un Apache sunucusu SSL kendinden imzalı bir sertifika gönderdi. Tarayıcı bunu fark etti ve bir güvenlik uyarısı görüntüledi: sunucunun sitesini güvenilir bulmuyor;
  • [4]'te devam ediyoruz;

Image

  • [6]'te devam ediyoruz;

Image

  • [7]'te, tarayıcı kullanıcının kimlik doğrulaması yapabilmesi için bir pencere görüntüler;
  • [9,10]'te, [admin] ve [admin] yazılır;

Image

  • [13]'te, sunucudan gelen jSON yanıtı;

Birkaç hata testi yapalım:

URL ve [https://localhost/php7/scripts-web/impots/version-08/impots-server.php?marié=x&enfants=x&salaire=x&w=x]'i talep ediyoruz

Şu sonucu alıyoruz:

Image

SGBD ve MySQL'i kesip URL ve [https://localhost/php7/scripts-web/impots/version-08/impots-server.php?marié=oui&enfants=3&salaire=60000]'i talep ediyoruz:

Image

18.2.5. [Codeception] testleri

Sunucunun yeni bir sürümünü her oluşturduğumuzda, sürüm 04'ten beri yapıldığı gibi (bkz. bağlantı ve bağlantı paragrafları) [métier] ve [dao] katmanlarını test edeceğiz.

Öncelikle, [scripts-web] projesini [Codeception] testleriyle ilişkilendiriyoruz. Bunun için, bağlantı paragrafında [scripts-console] projesi için izlenen prosedürü takip edin. [scripts-web] adlı bir proje ve [Test Files] adlı bir klasör elde ediyoruz:

Image

[dao] katmanı için bir test ve [métier] katmanı için bir test oluşturacağız.

18.2.5.1. [dao] katmanının testleri

Image

[ServerDaoTest] testi şu şekilde olacaktır:


<?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:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-08");
// yapılandırma dosyasının yolu
define("CONFIG_FILENAME", ROOT . "/Data/config-server.json");

// yapılandırmayı alıyoruz
$config = \json_decode(\file_get_contents(CONFIG_FILENAME), true);
// komut dosyası için gerekli bağımlılıkları dahil etme
$rootDirectory = $config["rootDirectory"];
foreach ($config["relativeDependencies"] as $dependency) {
  require "$rootDirectory$dependency";
}
// mutlak bağımlılıklar (üçüncü taraf kütüphaneler)
foreach ($config["absoluteDependencies"] as $dependency) {
  require "$dependency";
}

// test -----------------------------------------------------

class ServerDaoTest extends \Codeception\Test\Unit {
  // TaxAdminData
  private $taxAdminData;

  public function __construct() {
    // üst öğe
    parent::__construct();
    // yapılandırma alınır
    $config = \json_decode(\file_get_contents(CONFIG_FILENAME), true);
    // katman oluşturma [dao]
    $dao = new ServerDao(ROOT . "/" . $config["databaseFilename"]);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

  // testler
  public function testTaxAdminData() {

  }

}

Yorumlar

  • 9-24. satırlar: [impots-server.php] sunucusundakiyle aynı çalışma ortamı oluşturulur. Bu, 9-12. satırlarda, ortamın bağlı olduğu iki sabitin tanımlanmasıyla gerçekleştirilir;
  • 32-40. satırlar: [impots-server.php] sunucu betiğinde yapıldığı gibi, test edilecek [dao] katmanının bir örneği oluşturulur;
  • bu noktadan itibaren [impots-server.php] sunucu betiğiyle aynı koşullarda bulunmaktayız: testlere başlayabiliriz;
  • satır 43-45: [testTaxAdminData] yöntemi, bağlantı paragrafında açıklanan yöntemdir;

Test sonuçları şu şekildedir:

Image

18.2.5.2. [métier] katman testleri

Image

[ServerMetierTest] testi şu şekilde olacaktır:


<?php

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

// ad alanı
namespace Application;

// sabitlerin tanımlanması
define("ROOT", "C:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-08");
// yapılandırma dosyasının yolu
define("CONFIG_FILENAME", ROOT . "/Data/config-server.json");
// yapılandırmayı alıyoruz
$config = \json_decode(\file_get_contents(CONFIG_FILENAME), true);
// komut dosyası için gerekli bağımlılıkları dahil etme
$rootDirectory = $config["rootDirectory"];
foreach ($config["relativeDependencies"] as $dependency) {
  require "$rootDirectory$dependency";
}
// mutlak bağımlılıklar (üçüncü taraf kütüphaneler)
foreach ($config["absoluteDependencies"] as $dependency) {
  require "$dependency";
}

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

  public function __construct() {
    parent::__construct();
    // yapılandırma alınır
    $config = \json_decode(\file_get_contents(CONFIG_FILENAME), true);
    // [dao] katmanının oluşturulması
    $dao = new ServerDao(ROOT . "/" . $config["databaseFilename"]);
    // katman oluşturma [métier]
    $this->métier = new ServerMetier($dao);
  }

  // testler
  public function test1() {

  }

  public function test2() {

  }

  ..

  public function test11() {

  }

}

Açıklamalar

  • 9-24. satırlar: [impots-server.php] sunucusundakiyle aynı çalışma ortamı oluşturulur. Bu, 9-12. satırlarda, ortamın bağlı olduğu iki sabitin tanımlanmasıyla gerçekleştirilir;
  • 30-38. satırlar: [impots-server.php] sunucu betiğinde yapıldığı gibi, test edilecek [métier] katmanının bir örneği oluşturulur;
  • bu noktadan itibaren [impots-server.php] sunucu betiğiyle aynı koşullarda bulunmaktayız: testlere başlayabiliriz;
  • satır 40-53: [test1, test2…, test11] yöntemleri, bağlantı paragrafında açıklanan yöntemlerdir;

Test sonuçları şu şekildedir:

Image

18.3. İstemci

Uygulamanın istemci tarafıyla ilgileniyoruz.

Image

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

Image

18.3.1. Katmanlar arasında alışverişi yapılan varlıklar

Image

Yukarıdaki varlıkların tümü açıklanmış ve halihazırda kullanılmıştır:

18.3.2. [dao] katmanı

Image

[dao] katmanı, aşağıdaki [InterfaceClientDao] arayüzünü uygular:


<?php

// ad alanı
namespace Application;

interface InterfaceClientDao {

  // vergi mükellefi verilerinin okunması
  public function getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array;

  // bir vergi mükellefinin vergilerinin hesaplanması
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;

  // sonuçların kaydedilmesi
  public function saveResults(string $resultsFilename, array $taxPayersData): void;
}
  • 9. satır: [getTaxPayersData] işlevi, [$taxPayersFilename] dosyasındaki vergi mükelleflerinin verilerini belleğe yükler. Hata olması durumunda, bunlar [$errorsFilename] dosyasına kaydedilir;
  • 12. satır: [calculerImpots] fonksiyonu, bir vergi mükellefinin vergisini hesaplar;
  • 15. satır: [saveResults] işlevi, çeşitli vergi hesaplamalarının sonuçlarını temsil eden [$taxPayersData] tablosundaki verileri [$resultsFilename] dosyasına kaydeder;

[InterfaceClientDao] arayüzü, aşağıdaki [ClientDao] sınıfı tarafından uygulanır:


<?php

namespace Application;

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

class ClientDao implements InterfaceClientDao {
  // Bir İşlem'in kullanılması
  use TraitDao;
  // özellikler
  private $urlServer;
  private $user;

  // 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 {
    // bir müşteri oluşturulur HTTP
    $httpClient = HttpClient::create([
        'auth_basic' => [$this->user["login"], $this->user["passwd"]],
        "verify_peer" => false
    ]);
    // sunucuya sorgu gönderiliyor
    $response = $httpClient->request('GET', $this->urlServer,
      ["query" => [
          "marié" => $marié,
          "enfants" => $enfants,
          "salaire" => $salaire
    ]]);
    // yanıt alınır
    $json = $response->getContent(false);
    $array = \json_decode($json, true);
    $réponse = $array["réponse"];
    // günlükler
    // "$json=json\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
      $réponse = ["statut HTTP" => $statusCode] + $réponse;
      $message = \json_encode($réponse, JSON_UNESCAPED_UNICODE);
      throw new ExceptionImpots($message);
    }
    // yanıtı döndürüyoruz
    return $réponse;
  }

}

Açıklamalar

  • 10. satır: [TraitDao] (bkz. bağlantı paragrafı) eklenir; bu sınıf, [getTaxPayersData] ve [saveResults] yöntemlerini uygular. Böylece geriye sadece [calculerImpots] yöntemi kalır. Bu yöntem 22-49. satırlarda uygulanmıştır;
  • 16-19. satırlar: [ClientDao] sınıfının oluşturucusu iki parametre alır:
    • vergi hesaplama sunucusundan gelen URL ve [$urlServer];
    • isteği yapan kullanıcıyı tanımlayan ‘login’ ve ‘passwd’ anahtarlarından oluşan [$user] dizisi;
  • 22. satır: [calculerImpots] yöntemi, vergi hesaplama sunucusuna gönderilecek üç parametreyi alır;
  • 24-27. satırlar: Aşağıdakilerle bir HTTP istemcisi oluşturulur:
    • 25. satır: İsteği gönderen kullanıcının kimlik bilgileri;
    • 26. satır: HTTP istemcisinin, sunucu tarafından gönderilen SSL sertifikasının geçerliliğini kontrol etmemesini sağlayan seçenek;
  • 29-34. satırlar: sunucuya, beklediği üç parametre ile sorgu gönderilir;
  • 36. satır: Sunucudan jSON yanıtı alınır. [false] parametresi [Response::getContent] yöntemine eklenmezse, sunucunun yanıt durumunun [3xx-5xx] aralığında olması durumunda (hata durumu), [Response] nesnesi, [Response::getContent] yanıtının içeriğini veya HTTP ve [Response::getHeaders] başlıklarını almaya çalışıldığında bir istisna oluşturur. Burada, yanıtın HTTP durumu ne olursa olsun, en azından günlüğe kaydetmek için (40. satır) yanıtın içeriğine erişebilmek istiyoruz;
  • 37-38. satırlar: Sunucunun yanıtı, [‘réponse’=>qqChose] dizisinden gelen jSON dizesidir. [qqChose] değerini alıyoruz;
  • 40. satır: jSON yanıtını geliştirme modunda günlüğe kaydediyoruz;
  • 42. satır: Yanıtın durum kodunu alıyoruz;
  • satır 44-49: HTTP durum kodu 200 değilse, sunucumuzda bir sorun meydana gelmiştir. Bu durumda, mesaj olarak sunucudan gelen jSON yanıtına yanıt kodunu HTTP ekleyerek [ExceptionImpots] türünde bir istisna tetiklenir;
  • 51. satır: [impôt, surcôte, décôte, réduction, taux] anahtarlarına sahip bir ilişkisel dizi olan sonucu döndürüyoruz;

18.3.3. [métier] katmanı

Image

Image

[métier] [8] katmanı, aşağıdaki [InterfaceClientMetier] arayüzünü uygular:


<?php

// ad alanı
namespace Application;

interface InterfaceClientMetier {

  // bir vergi mükellefinin vergilerinin hesaplanması
  public function calculerImpot(string $marié, int $enfants, int $salaire): array;

  // toplu iş modunda vergi hesaplaması
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void;
}
  • 9. satır: [calculerImpots] işlevi vergiyi hesaplar;
  • 12. satır: [executeBatchImpots] işlevi, verileri [$taxPayersFileName] dosyasında bulunan mükelleflerin vergisini hesaplar, elde edilen sonuçları [$resultsFileName] dosyasına, karşılaşılan hataları ise [$errorsFileName] dosyasına kaydeder;

[InterfaceClientMetier] arayüzü, aşağıdaki [ClientMetier] sınıfı tarafından uygulanır:


<?php

// ad alanı
namespace Application;

class ClientMetier implements InterfaceClientMetier {
  // özellik
  private $clientDao;

  // oluşturucu
  public function __construct(InterfaceClientDao $clientDao) {
    // referans, [dao] katmanında saklanır
    $this->clientDao = $clientDao;
  }
  
  // vergi hesaplaması
  public function calculerImpot(string $marié, int $enfants, int $salaire): array {
    return $this->clientDao->calculerImpot($marié, $enfants, $salaire);
  }

  // toplu iş modunda vergi hesaplaması
  public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
    // [dao] katmanından gelen istisnaların yukarıya iletilmesine izin verilir
    // vergi mükellefi verilerini alın
    $taxPayersData = $this->clientDao->getTaxPayersData($taxPayersFileName, $errorsFileName);
    // sonuç tablosu
    $results = [];
    // veriler işlenir
    foreach ($taxPayersData as $taxPayerData) {
      // vergi hesaplanıyor
      $result = $this->calculerImpot(
        $taxPayerData->getMarié(),
        $taxPayerData->getEnfants(),
        $taxPayerData->getSalaire());
      // formu doldurulur [$taxPayerData]
      $taxPayerData->setFromArrayOfAttributes($result);
      // sonuç sonuç tablosuna ekleniyor
      $results [] = $taxPayerData;
    }
    // sonuçların kaydedilmesi
    $this->clientDao->saveResults($resultsFileName, $results);
  }

}

Yorumlar

  • 11-14. satırlar: [ClientMetier] sınıfının oluşturucusu, [dao] katmanına bir referans parametre olarak alır;
  • satır 17-19: Vergi hesaplaması, [dao] katmanına devredilir;
  • satır 20-38: [executeBatchImpots] işlevi, “bağlantı” başlıklı paragrafta açıklanmıştır;

18.3.4. Ana komut dosyası

Image

Image

[MainImpotsClient.php] istemci komut dosyası, [console] ve [9] katmanlarını uygular. Aşağıdaki jSON ve [conf-client.json] dosyalarıyla yapılandırılır:


{
    "rootDirectory": "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-08",
    "taxPayersDataFileName": "Data/taxpayersdata.json",
    "resultsFileName": "Data/results.json",
    "errorsFileName": "Data/errors.json",
    "dependencies": [
        "Entities/BaseEntity.php",
        "Entities/TaxPayerData.php",
        "Entities/ExceptionImpots.php",
        "Utilities/Utilitaires.php",
        "Dao/InterfaceClientDao.php",
        "Dao/TraitDao.php",
        "Dao/ClientDao.php",
        "Métier/InterfaceClientMetier.php",
        "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-08/impots-server.php"
}
  • 1. satır: müşterinin kök klasörü;
  • 2. satır: vergi mükellefi verilerini içeren jSON dosyası;
  • 3. satır: sonuçların bulunduğu jSON dosyası;
  • satır 4: hataların bulunduğu jSON dosyası;
  • satır 6-19: müşteri projesinin çeşitli bağımlılıkları;
  • satır 20-23: vergi hesaplama sunucusuna sorgu gönderen kullanıcı;
  • 24. satır: vergi hesaplama sunucusunun güvenli URL dosyası;

[MainImpotsClient.php] komut dosyasının kodu şöyledir:


<?php

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

// ad alanı
namespace Application;

// PHP ile hata yönetimi
//ini_set("display_errors", "0");
//
// yapılandırma dosyasının yolu
define("CONFIG_FILENAME", "../Data/config-client.json");

// yapılandırma alınır
$config = \json_decode(file_get_contents(CONFIG_FILENAME), true);

// komut dosyası için gerekli bağımlılıkları ekleniyor
$rootDirectory = $config["rootDirectory"];
foreach ($config["dependencies"] as $dependency) {
  require "$rootDirectory/$dependency";
}
// mutlak bağımlılıklar (üçüncü taraf kütüphaneler)
foreach ($config["absoluteDependencies"] as $dependency) {
  require "$dependency";
}

// sabitlerin tanımı
define("TAXPAYERSDATA_FILENAME", "$rootDirectory/{$config["taxPayersDataFileName"]}");
define("RESULTS_FILENAME", "$rootDirectory/{$config["resultsFileName"]}");
define("ERRORS_FILENAME", "$rootDirectory/{$config["errorsFileName"]}");
//
// Symfony bağımlılıkları
use Symfony\Component\HttpClient\HttpClient;

// katman oluşturma [dao]
$clientDao = new ClientDao($config["urlServer"], $config["user"]);
// katman oluşturma [métier]
$clientMetier = new ClientMetier($clientDao);

// toplu iş modunda vergi hesaplaması
try {
  $clientMetier->executeBatchImpots(TAXPAYERSDATA_FILENAME, RESULTS_FILENAME, ERRORS_FILENAME);
} catch (\RuntimeException $ex) {
  // hatanın görüntülenmesi
  print "L'erreur suivante s'est produite : " . $ex->getMessage() . "\n";
}
// son
print "Terminé\n";
exit;

Açıklamalar

  • 13. satır: yapılandırma dosyasının yolu;
  • 16. satır: yapılandırma dosyasının işlenmesi;
  • 18-26. satırlar: bağımlılıkların yüklenmesi;
  • satır 37: [dao] katmanının oluşturulması. Katman oluşturucusuna, beklediği iki bilgiyi aktarıyoruz:
    • vergi hesaplama sunucusunun URL kimliği;
    • istekleri yapacak kullanıcının kimlik bilgileri;
  • 39. satır: [métier] katmanının oluşturulması. Katman oluşturucusuna, az önce oluşturulan [dao] katmanına bir referans aktarılır;
  • 43. satır: [métier] katmanından şunları yapması istenir:
    • $config["taxPayerDataFileName"] dosyasındaki tüm vergi mükelleflerinin vergilerini hesaplaması;
    • sonuçları $config["resultsFileName"] dosyasına kaydetmesi;
    • hataları $config["errorsFileName"] dosyasına kaydetmek;
  • 43. satır istisna oluşturabilir;
  • 46. satır: istisna hata mesajının görüntülenmesi;

İstemcinin çalıştırılması önceki sürümlerle aynı sonuçları verir. Aşağıdaki dosyaları kontrol edin:

  • [Data/taxpayersdata.json]: vergi tutarı hesaplanan vergi mükelleflerinin verileri;
  • [Data/results.json]: [Data/taxpayersdata.json] dosyasındaki farklı vergi mükelleflerine ait sonuçlar;
  • [Data/errors.json]: [Data/taxpayersdata.json] dosyasının işlenmesi sırasında karşılaşılmış olabilecek hatalar;

Olası hata durumlarına bir göz atalım. Öncelikle, Laragon sunucusunu durduralım. Bu durumda istemci konsolundaki sonuçlar şu şekildedir:


Couldn't connect to server for"https://localhost/php7/scripts-web/impots/version-08/impots-server.php?mari%C3%A9=oui&enfants=2&salaire=55555".
Terminé

Şimdi sadece Apache sunucusunu başlatalım, SGBD ve MySQL sunucularını başlatmayalım:

Image

İstemci konsolundaki sonuçlar şu şekildedir:


L'erreur suivante s'est produite : {"statut HTTP":500,"erreur":"SQLSTATE[HY000] [2002] Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée.\r\n"}
Terminé

Şimdi MySQL'i çalıştıralım ve ardından [config-client]'te oturum açan kullanıcıyı değiştirelim:

1
2
3
4
    "user": {
        "login": "x",
        "passwd": "x"
},

Müşteri konsolundaki sonuçlar şu şekildedir:


L'erreur suivante s'est produite : {"statut HTTP":401,"erreur":"Echec de l'authentification [x, x]"}
Terminé

18.3.5. [Codeception] testleri

Önceki sürümlerde olduğu gibi, 08 sürümü için [Codeception] testleri yazacağız.

Image

18.3.5.1. [métier] katman testi

[ClientMetierTest.php] testi şu şekildedir:


<?php

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

// ad alanı
namespace Application;

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

// 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);

// komut dosyası için gerekli bağımlılıkları dahil etme
$rootDirectory = $config["rootDirectory"];
foreach ($config["dependencies"] as $dependency) {
  require "$rootDirectory/$dependency";
}
// mutlak bağımlılıklar (üçüncü taraf kütüphaneler)
foreach ($config["absoluteDependencies"] as $dependency) {
  require "$dependency";
}
//
// test sınıfı
class ClientMetierTest extends \Codeception\Test\Unit {
  // iş mantığı katmanı
  private $métier;

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

  // testler
  public function test1() {

  }

  -------------

  public function test11() {

  }

}

Açıklamalar

  • 10-26. satırlar: test ortamının tanımlanması. “Bağlantı” başlıklı paragrafta açıklanan ana komut dosyası [MainImpotsClient] ile aynı ortamı kullanıyoruz;
  • 33-41. satırlar: [dao] ve [métier] katmanlarının oluşturulması;
  • 40. satır: [$this→métier] özniteliği, [métier] katmanına atıfta bulunur;
  • satır 44-51: [test1, test2…, test11] yöntemleri, bağlantı paragrafında açıklanan yöntemlerdir;

Test sonuçları şöyledir:

Image