Skip to content

19. Uygulama alıştırması – sürüm 9

Bu sürümde, sunucuyu aşağıdaki şekilde iyileştireceğiz:

  • Şu anda, her istek geldiğinde vergi idaresine ait veriler veritabanında aranmaktadır. Bir oturum kullanacağız:
    • Bir kullanıcının ilk isteğinde, vergi idaresine ait veriler veritabanında aranır ve oturuma aktarılır;
    • aynı kullanıcının sonraki isteklerinde, vergi idaresine ait veriler oturumdan aranır. Veritabanı sorguları maliyetli olduğundan, yürütme süresinde hafif bir kazanç elde edilebilir;
  • sunucu, önemli anları bir metin dosyasına kaydedecektir:
    • başarılı veya başarısız kimlik doğrulama;
    • istemci tarafından gönderilen parametrelerin geçerli olup olmadığı;
    • vergi hesaplamasının sonucu;
    • çeşitli hata durumları;
  • ciddi bir hata durumunda, uygulama yöneticisine bir e-posta gönderilecektir;

İstemci de, kendisine gönderilecek oturum çerezini yönetebilmek için değiştirilmelidir.

19.1. Sunucu

Uygulamanın sunucu tarafıyla ilgileniyoruz.

Image

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

Image

19.1.1. Yardımcı programlar

Image

19.1.1.1. [Logger] sınıfı

[Logger] sınıfı, bir metin dosyasına günlük kaydı yazmak için kullanılacaktır:


<?php

namespace Application;

class Logger {
  // özellik
  private $resource;

  // oluşturucu
  public function __construct(string $logsFilename) {
    // dosyanın açılması
    $this->resource = fopen($logsFilename, "a");
    if (!$this->resource) {
      throw new ExceptionImpots("Echec lors de la création du fichier de logs [$logsFilename]");
    }
  }

  // günlük dosyalarına mesaj yazma
  public function write(string $message) {
    fputs($this->resource, (new \DateTime())->format("d/m/y H:i:s:v") . " : $message");
  }

  // günlük dosyasının kapatılması
  public function close() {
    fclose($this->resource);
  }

}

Açıklamalar

  • 7. satır: günlük dosyasının kaynağı;
  • 10. satır: sınıfın oluşturucusu, parametre olarak günlük dosyasının adını alır;
  • 12. satır: metin dosyası ekleme modunda (a+) açılır: dosya açılır, içeriği korunur. Yazma işlemleri mevcut içeriğin sonuna eklenir;
  • 13-15. satırlar: dosya açılamazsa bir istisna atılır;
  • satır 19-21: [write] yöntemi, tarih ve saat bilgisinin önüne eklenmiş [$message] mesajını günlük dosyasına yazmaya olanak tanır;
  • 24-16. satırlar: [close] yöntemi, günlük dosyasını kapatmaya olanak tanır;

Not: Sunucu uygulaması aynı anda birden fazla istemciye hizmet verebilir. Ancak hepsi için tek bir günlük dosyası bulunmaktadır. Dolayısıyla, dosyaya yazma işlemleri sırasında eşzamanlı erişim riski söz konusudur. Bu nedenle, yazma işlemlerinin birbirine karışmasını önlemek için senkronize edilmesi gerekir. Bunun için PHP sınıfı, [https://www.php.net/manual/fr/book.sem.php] semaforlarını kullanır. Burada yazma işlemlerinin senkronizasyonunu göz ardı edeceğiz, ancak bu sorunun farkında olmak gerekir.

19.1.1.2. [SendAdminMail] sınıfı

[SendAdminMail] sınıfı, uygulamanın çökmesi durumunda uygulama yöneticisine bir e-posta gönderilmesini sağlar:


<?php

namespace Application;

class SendAdminMail {
  // özellikler
  private $config;
  private $logger;

  // oluşturucu
  public function __construct(array $config, Logger $logger = NULL) {
    $this->config = $config;
    $this->logger = $logger;
  }

  public function send() {
    // $this->config['message'] dosyasını $this SMTP sunucusuna gönderir->config['smtp-server'], $infos[smt-port] numaralı bağlantı noktası üzerinden
    // eğer $this->config['tls'] doğruysa, TLS desteği kullanılacaktır
    // e-posta, $this->config['from'] adına gönderilir
    // alıcı $this->config['to'] için
    // mesajın konusu $this->config['subject']
    // e-postaya $this->config['attachments'] ekleri eklenir
    // yöntemin sonucu
    try {
      // mesajın oluşturulması
      $message = (new \Swift_Message())
        // mesajın konusu
        ->setSubject($this->config["subject"])
        // gönderen
        ->setFrom($this->config["from"])
        // sözlük içeren alıcılar (setTo/setCc/setBcc)
        ->setTo($this->config["to"])
        // mesaj metni
        ->setBody($this->config["message"])
      ;
      // ekler
      foreach ($this->config["attachments"] as $attachment) {
        // eki yol
        $fileName = __DIR__ . $attachment;
        // dosyanın var olup olmadığı kontrol edilir
        if (file_exists($fileName)) {
          // belge mesaja eklenir
          $message->attach(\Swift_Attachment::fromPath($fileName));
        } else {
          if ($this->logger !== NULL) {
            // hata
            $this->logger->write("L'attachement [$fileName] n'existe pas\n");
          }
        }
      }
      // TLS protokolü mü?
      if ($this->config["tls"] === "TRUE") {
        // TLS
        $transport = (new \Swift_SmtpTransport($this->config["smtp-server"], $this->config["smtp-port"], 'tls'))
          ->setUsername($this->config["user"])
          ->setPassword($this->config["password"]);
      } else {
        // TLS yok
        $transport = (new \Swift_SmtpTransport($this->config["smtp-server"], $this->config["smtp-port"]));
      }
      // gönderim yöneticisi
      $mailer = new \Swift_Mailer($transport);
      // mesajın gönderilmesi
      $mailer->send($message);
      // son
      if ($this->logger !== NULL) {
        $this->logger->write("Message [{$this->config["message"]}] envoyé à {$this->config["to"]}\n");
      }
    } catch (\Throwable $ex) {
      // hata
      if ($this->logger !== NULL) {
        $this->logger->write("Erreur lors de l'envoi du message [{$this->config["message"]}] à {$this->config["to"]}\n");
      }
    }
  }

}

Açıklamalar

  • 11. satır: Oluşturucu iki parametre alır:
    • [$config]: e-postanın gönderilmesi için gerekli tüm bilgileri içeren bir ilişkisel dizi;
    • [$logger]: e-posta gönderimi sırasında önemli anları günlüğe kaydetmeye yarayan bir logger;

İlişkisel dizi aşağıdaki şekilde olacaktır:

1
2
3
4
5
6
7
8
9
{
        "smtp-server": "localhost",
        "smtp-port": "25",
        "from": "guest@localhost",
        "to": "guest@localhost",
        "subject": "plantage du serveur de calcul d'impôts",
        "tls": "FALSE",
        "attachments": []
}
  • 16-76. satırlar: [send] yöntemi, e-postanın gönderilmesini sağlar. Bu kod, bağlantı paragrafında sunulmuş ve açıklanmıştır;

19.1.2. [dao] katmanı

Image

[ServeurDaoWithSession.php] komut dosyası şu şekildedir:


<?php

// ad alanı
namespace Application;

// sınıf tanımı ImpotsWithDataInDatabase
class ServerDaoWithSession extends ServerDao {

  // yapıcı
  public function __construct(string $databaseFilename = NULL, TaxAdminData $taxAdminData = NULL) {
    // en basit durum
    if ($taxAdminData !== NULL) {
      $this->taxAdminData = $taxAdminData;
    } else {
      // kontrol üst sınıfa devredilir
      parent::__construct($databaseFilename);
    }
  }

}

Açıklamalar

  • 7. satır: 09 sürümündeki [ServerDaoWithSession] sınıfı, 08 sürümündeki [ServerDao] sınıfını genişletir. Nitekim, [ServerDao] sınıfı veritabanını kullanmayı bilir. Geriye kalan tek şey, vergi idaresinden gelen verilerin halihazırda elde edilmiş olduğu durumu dikkate almaktır:
  • 10. satır: Oluşturucu artık iki parametre alır:
    • [string $databaseFilename]: vergi idaresine ait veriler henüz alınmamışsa veritabanına bağlanmak için gerekli bilgileri içeren dosyanın adı; aksi takdirde NULL;
    • [TaxAdminData $taxAdminData]: vergi dairesinin verileri (eğer daha önce alınmışsa), aksi takdirde NULL;

Bir web oturumu başlatıldığında, [dao] katmanı, NULL olmayan bir [$databaseFilename] nesnesi ve NULL olan bir [taxAdminData] nesnesi ile oluşturulur. Daha sonra vergi idaresine ait veriler veritabanında aranacak ve oturumda saklanacaktır. Aynı oturumdaki sonraki isteklerde, [dao] katmanı, oturumdan gelen bir [databaseFilename], NULL ve bir [taxAdminData] nesnesi ile oluşturulacak ve NULL kullanılmayacaktır. Dolayısıyla veritabanında herhangi bir arama yapılmayacaktır.

19.1.3. Sunucu komut dosyası

[impots-server.php] sunucu komut dosyası, aşağıdaki jSON ve [config-server.json] dosyalarıyla yapılandırılır:


{
    "rootDirectory": "C:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-09",
    "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",
        "/Dao/ServerDaoWithSession.php",
        "/../version-08/Métier/InterfaceServerMetier.php",
        "/../version-08/Métier/ServerMetier.php",
        "/Utilities/Logger.php",
        "/Utilities/SendAdminMail.php"
    ],
    "absoluteDependencies": ["C:/myprograms/laragon-lite/www/vendor/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"
}

[impots-server.php] sunucu komut dosyası şu şekilde gelişmektedir:


<?php

// fonksiyon parametrelerinin bildirilen 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";
}
//
// Symfony bağımlılıkları
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;

// oturum
$session = new Session();
$session->start();

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

// günlük dosyasının oluşturulması
try {
  $logger = new Logger($config['logsFilename']);
} catch (ExceptionImpots $ex) {
  // iç sunucu hatası
  doInternalServerError($ex->getMessage(), $response, NULL, $config['adminMail']);
  // tamamlandı
  exit;
}

// 1. günlük
$logger->write("\n---nouvelle requête\n");

// geçerli istek alınmaktadır
$request = Request::createFromGlobals();
// sadece ilk seferde kimlik doğrulama
if (!$session->has("user")) {
  // günlük
  $logger->write("Autentification en cours…\n");
  // kimlik doğrulama

  }
  // kullanıcı bulundu mu?
  if (!$trouvé) {
    // bulunamadı - kod 401 HTTP_UNAUTHORIZED
    sendResponse(
      $response,
      ["erreur" => "Echec de l'authentification [$requestUser, $requestPassword]"],
      Response::HTTP_UNAUTHORIZED,
      ["WWW-Authenticate" => "Basic realm=" . utf8_decode("\"Serveur de calcul d'impôts\"")],
      $logger
    );
    // işlem tamamlandı
    exit;
  } else {
    // kullanıcının kimlik doğrulamasının yapıldığı oturum kaydına işlenir
    $session->set("user", TRUE);
    // günlük
    $logger->write("Authentification réussie [$requestUser, $requestPassword]\n");
  }
} else {
  // günlük
  $logger->write("Authentification prise en session…\n");
}
// geçerli bir kullanıcı var - alınan parametreler kontrol ediliyor
$erreurs = [];
// üç parametre olması gerekiyor: GET


// hatalar mı var?
if ($erreurs) {
// Müşteriye 400 hata kodu gönderiliyor: HTTP_BAD_REQUEST
  sendResponse($response, ["erreurs" => $erreurs], Response::HTTP_BAD_REQUEST, [], $logger);
  // işlem tamamlandı
  exit;
} else {
  // günlükler
  $logger->write("paramètres ['marié'=>$marié, 'enfants'=>$enfants, 'salaire'=>$salaire] valides\n");
}
// Çalışmak için gereken her şeye sahibiz
// katman oluşturma [dao]
if (!$session->has("taxAdminData")) {
  // veriler veritabanından alınır
  $logger->write("données fiscales prises en base de données\n");
  try {
    // katmanın oluşturulması [dao]
    $dao = new ServerDaoWithSession($config["databaseFilename"], NULL);
    // veriler oturuma aktarılıyor
    $session->set("taxAdminData", $dao->getTaxAdminData());
  } catch (\RuntimeException $ex) {
    // hata kaydediliyor
    doInternalServerError(utf8_encode($ex->getMessage()), $response, $logger, $config['adminMail']);
    // işlem tamamlandı
    exit;
  }
} else {
  // veriler oturumdan alınır
  $dao = new ServerDaoWithSession(NULL, $session->get("taxAdminData"));
  // günlükler
  $logger->write("données fiscales prises en session\n");
}
// katman oluşturuluyor [métier]
$métier = new ServerMetier($dao);
// vergi hesaplaması
$result = $métier->calculerImpot($marié, (int) $enfants, (int) $salaire);
// yanıt veriliyor
sendResponse($response, $result, Response::HTTP_OK, [], $logger);
// son
exit;

function doInternalServerError(string $message, Response $response, Logger $logger = NULL, array $infos) {
  // yöneticiye e-posta gönderiliyor
  // SendAdminMail tüm istisnaları yakalar ve bunları kendisi günlüğe kaydeder
  $infos['message'] = $message;
  $sendAdminMail = new SendAdminMail($infos, $logger);
  $sendAdminMail->send();
  // müşteriye 500 hata kodu gönderilir
  sendResponse($response, ["erreur" => $message], Response::HTTP_INTERNAL_SERVER_ERROR, [], $logger);
}

// HTTP yanıtını müşteriye gönderme işlevi
function sendResponse(Response $response, array $result, int $statusCode, array $headers, Logger $logger) {
  // $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üğü
  //
  // HTTTP durumu
  $response->setStatusCode($statusCode);
  // gövde
  $body = \json_encode(["réponse" => $result], JSON_UNESCAPED_UNICODE);
  $response->setContent($body);
  // başlıklar
  $response->headers->add($headers);
  // gönderim
  $response->send();
  // günlük
  if ($logger != NULL) {
    $logger->write("$body\n");
    $logger->close();
  }
}

Açıklamalar

  • 34-35. satırlar: bir oturum başlatılır;
  • 38-40. satırlar: jSON yanıtı hazırlanır;
  • 42-50. satırlar: günlük dosyası oluşturulmaya çalışılır. Bir istisna oluşursa, [doInternalServer] yöntemi (132-140. satırlar) çağrılır;
  • 132. satır: [doInternalServer] yöntemi dört parametre kabul eder:
    • [$message]: Günlüğe kaydedilecek mesaj. UTF-8 formatında kodlanmalıdır;
    • [$response]: Sunucunun istemcisine verdiği yanıtı kapsayan [Response] nesnesi;
    • [$logger]: Günlük kaydı oluşturmaya yarayan [Logger] nesnesi;
    • [$infos]: uygulama yöneticisine e-posta göndermek için gerekli bilgiler;
  • 135-137. satırlar: uygulama yöneticisine e-posta gönderilir;
  • 139. satır: müşteriye yanıt gönderilir:
    • $response: HTTP yanıtı;
    • $result: sunucu, [‘réponse’=>["erreur" => $message]] dizisinden jSON dizesini gönderir;
    • $statusCode: [Response::HTTP_INTERNAL_SERVER_ERROR], kod 500;
    • $headers: [], yanıtta eklenecek HTTP başlığı yok;
    • $logger: uygulamanın günlüğü;
  • 58. satır: Oluşturulan oturum sayesinde, istemcinin kimlik doğrulaması yalnızca bir kez yapılacaktır:
    • müşteri kimlik doğrulaması yapıldıktan sonra, oturuma bir anahtar [user] eklenecektir (78. satır);
    • aynı müşteriden gelen bir sonraki istek sırasında, 58. satır artık gereksiz hale gelen kimlik doğrulamayı önler;
  • 103. satır: Oluşturulan oturum sayesinde veritabanında veri araması yalnızca bir kez yapılır:
    • ilk istek sırasında veritabanında arama yapılır (satır 108). Alınan veriler daha sonra [taxAdminData] anahtarıyla ilişkilendirilerek oturuma eklenir (satır 110);
    • sonraki isteklerde, [taxAdminData] anahtarı oturumda bulunacak (satır 103) ve böylece veritabanındaki veriler doğrudan [dao] katmanına aktarılacaktır (satır 119);
  • satır 111-116: Veritabanında vergi verilerinin aranması başarısız olabilir. Bu durumda, müşteriye [500 Internal Server Error] kodu gönderilir;
  • satır 113: MySQL sürücüsünün istisna hata mesajı, ISO 8859-1 olarak kodlanmıştır. Düzgün bir şekilde günlüğe kaydedilebilmesi için bu kod, UTF-8'e dönüştürülür;
  • kodun geri kalanı önceki sürümünkiyle neredeyse aynıdır;
  • 143-164. satırlar: [sendResponse] işlevi tüm yanıtları istemciye gönderir;
  • 144-148. satırlar: parametrelerin anlamları;
  • 153. satır: Yanıt her zaman [‘résultat’=>qqChose] dizisinden alınan jSON dizesidir;
  • satır 156: Bazen yanıtın içine eklenmesi gereken HTTP başlıkları olabilir. Satır 71'de durum böyledir;
  • satır 158: yanıt gönderilir;
  • satır 160-163: yanıt günlüğe kaydedilir ve günlük kaydedici kapatılır;

19.1.4. [Codeception] Testleri

Image

Sadece değişen tek katman olan [dao] katmanını test edeceğiz.

[ServerDaoTest] test kodunun içeriği şöyledir:


<?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-09");
// yapılandırma dosyasının yolu
define("CONFIG_FILENAME", ROOT . "/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ı 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 ServerDaoWithSession(ROOT . "/" . $config["databaseFilename"]);
    $this->taxAdminData = $dao->getTaxAdminData();
  }

  // testler
  public function testTaxAdminData() {

  }

}
  • 9-24. satırlar: [impots-server] sunucu betiğinkiyle aynı bir yürütme ortamı oluşturulur;
  • 38. satır: [dao] katmanını oluşturmak için [ServerDaoWithSession] sınıfı örneklenir;

Test sonuçları şöyledir:

Image

19.2. İstemci

Uygulamanın istemci tarafıyla ilgileniyoruz.

Image

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

Image

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

  • [config-client.json] yapılandırma dosyası;
  • istemcinin [dao] katmanı;

19.2.1. [dao] katmanı

[Dao] katmanı şu şekilde değişmektedir:


<?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 {
    // oturum çerezi mi?
    if (!$this->sessionCookie) {
      // bir müşteri oluşturulur HTTP
      $httpClient = HttpClient::create([
          'auth_basic' => [$this->user["login"], $this->user["passwd"]],
          "verify_peer" => false
      ]);
      // oturum çerezi olmadan sunucuya istek gönderiliyor
      $response = $httpClient->request('GET', $this->urlServer,
        ["query" => [
            "marié" => $marié,
            "enfants" => $enfants,
            "salaire" => $salaire
          ]
      ]);
    } else {
      // sunucuya oturum çerezi ile istek gönderiliyor
      // HTTP adlı bir istemci oluşturulur
      $httpClient = HttpClient::create([
          "verify_peer" => false
      ]);
      $response = $httpClient->request('GET', $this->urlServer,
        ["query" => [
            "marié" => $marié,
            "enfants" => $enfants,
            "salaire" => $salaire
          ],
          "headers" => ["Cookie" => $this->sessionCookie]
      ]);
    }
    // yanıt alınır
    $json = $response->getContent(false);
    $array = \json_decode($json, true);
    $réponse = $array["réponse"];
    // günlükler
    print "$json=json\n";
    // yanıtın durumunu alınıyor
    $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);
    }
    if (!$this->sessionCookie) {
      // oturum çerezi alınır
      $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ı gönderiyoruz
    return $réponse;
  }

}

Yorumlar

[dao] katmanındaki değişiklik, artık bir oturumu yönetmeyi içermektedir:

  • 14. satır: oturum çerezi;
  • 25-39. satırlar: ilk istek sırasında bu çerez mevcut değildir; bu durumda, kimlik doğrulama bilgilerini göndererek (28. satır) sunucuya istek gönderilir;
  • 40-53. satırlar: sonraki isteklerde normalde oturum çerezi mevcuttur. Bu durumda kimlik doğrulama bilgileri gönderilmez (42-44. satırlar);
  • satır 69-82: Sunucunun ilk isteğe verdiği yanıtta bir oturum çerezi bulunur. Bu çerez alınır. Bu kod, bağlantı paragrafında daha önce kullanılmış ve açıklanmıştır;
  • 78. satır: Alınan oturum çerezi, [$sessionCookie] sınıf özniteliğinde saklanır;

Not: [dao] katmanının eski sürümünü koruyup her istekte kimlik doğrulaması yapabilirdik, çünkü bunun maliyeti ihmal edilebilir düzeydedir. Eğitim amaçlı olarak, bir HTTP istemcisinin bir oturumu nasıl yönetebileceğini hatırlatmak istedik.

19.2.2. Yapılandırma dosyası

jSON yapılandırma dosyası şu şekilde değişir:


{
    "rootDirectory": "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-09",
    "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-09/impots-server.php"
}

Yalnızca 24. satırdaki URL değişir.

19.3. Bazı testler

19.3.1. Test 1

Öncelikle, istemciyi hatasız bir ortamda çalıştırıyoruz. Sonuçlar her zamanki gibi önceki sürümlerinkiyle aynı. Ancak şimdi sunucu tarafında [logs.txt] adlı bir günlük dosyası var:


04/07/19 13:16:08:523 :
---nouvelle requête
04/07/19 13:16:08:529 : Autentification en cours
04/07/19 13:16:08:529 : Authentification réussie [admin, admin]
04/07/19 13:16:08:529 : paramètres ['marié'=>oui, 'enfants'=>2, 'salaire'=>55555] valides
04/07/19 13:16:08:529 : tranches d'impôts prises en base de données
04/07/19 13:16:08:534 : {"réponse":{"impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}}
04/07/19 13:16:08:643 :
---nouvelle requête
04/07/19 13:16:08:648 : Authentification prise en session
04/07/19 13:16:08:648 : paramètres ['marié'=>oui, 'enfants'=>2, 'salaire'=>50000] valides
04/07/19 13:16:08:648 : tranches d'impôts prises en session
04/07/19 13:16:08:648 : {"réponse":{"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}}
04/07/19 13:16:08:769 :
---nouvelle requête
04/07/19 13:16:08:775 : Authentification prise en session
04/07/19 13:16:08:775 : paramètres ['marié'=>oui, 'enfants'=>3, 'salaire'=>50000] valides
04/07/19 13:16:08:775 : tranches d'impôts prises en session
04/07/19 13:16:08:775 : {"réponse":{"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}}
04/07/19 13:16:08:888 :
---nouvelle requête

  • 3-7. satırlar: İlk istekte kimlik doğrulama yapılır ve veritabanında arama gerçekleştirilir;
  • 9-14. satırlar: Bir sonraki istek sırasında kimlik doğrulama yapılmaz ve veriler oturumdan alınır. Bu durum sonraki isteklerde de tekrarlanır (15. satır ve sonrası);

19.3.2. Test 2

Şimdi MySQL veritabanını kapatalım. İstemci tarafında şu konsol sonucu elde edilir:


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é

Sunucu tarafında ise şu [logs.txt] günlükleri görüntülenir:


04/07/19 13:19:52:396 :
---nouvelle requête
04/07/19 13:19:52:405 : Autentification en cours…
04/07/19 13:19:52:405 : Authentification réussie [admin, admin]
04/07/19 13:19:52:405 : paramètres ['marié'=>oui, 'enfants'=>2, 'salaire'=>55555] valides
04/07/19 13:19:52:405 : tranches d'impôts prises en base de données
04/07/19 13:19:54:461 : {"réponse":{"erreur":"SQLSTATE[HY000] [2002] Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée.\r\n"}}
04/07/19 13:19:55:602 : Message [SQLSTATE[HY000] [2002] Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée.
] envoyé à guest@localhost
04/07/19 13:19:55:706 :
---nouvelle requête

Uygulama yöneticisinin aldığı e-postayı görmek için, aşağıdaki [config-imap-01.json] yapılandırma dosyasıyla bağlantılı paragrafta yer alan [imap-03.php] komut dosyasını kullanıyoruz:

{
    "{localhost:110/pop3}": {
        "imap-server": "localhost",
        "imap-port": "110",
        "user": "guest@localhost",
        "password": "guest",
        "pop3": "TRUE",
        "output-dir": "output/localhost-pop3"
    }
}

Sonuç şu şekilde olur:

Image

[message_1.txt] dosyası aşağıdaki metni içerir:


return-path: guest@localhost
received: from localhost (localhost [127.0.0.1]) by DESKTOP-528I5CU with ESMTP ; Thu, 4 Jul 2019 15:20:22 +0200
message-id: <c82d26df5fb352e10a51577cd1b9ed87@localhost>
date: Thu, 04 Jul 2019 13:20:20 +0000
subject: plantage du serveur de calcul d'impôts
from: guest@localhost
to: guest@localhost
mime-version: 1.0
content-type: text/plain; charset=utf-8
content-transfer-encoding: quoted-printable

SQLSTATE[HY000] [2002] Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée.

19.3.3. Test 3

Şimdi [logs.txt] dosyasının oluşturulmamasını sağlayalım. Bunun için [logs.txt] adlı bir klasör oluşturmak yeterlidir:

Image

Bunu yaptıktan sonra, istemciyi çalıştıralım.

İstemci tarafında şu konsol çıktıları elde edilir:


L'erreur suivante s'est produite : {"statut HTTP":500,"erreur":"Echec lors de la création du fichier de logs [Data\/logs.txt]"}
Terminé

Sunucu tarafında herhangi bir günlük kaydı bulunmamaktadır, ancak yönetici aşağıdaki e-postayı alır:


return-path: guest@localhost
received: from localhost (localhost [127.0.0.1]) by DESKTOP-528I5CU with ESMTP ; Thu, 4 Jul 2019 15:31:49 +0200
message-id: <b2cee274f3437952231d62152ba1cdb3@localhost>
date: Thu, 04 Jul 2019 13:31:48 +0000
subject: plantage du serveur de calcul d'impôts
from: guest@localhost
to: guest@localhost
mime-version: 1.0
content-type: text/plain; charset=utf-8
content-transfer-encoding: quoted-printable

Echec lors de la création du fichier de logs [Data/logs.txt]

19.3.4. Test 4

Bu sefer, istemcinin yapılandırma dosyasında, bağlanan istemciye hatalı kimlik bilgileri verelim.

İstemci, konsolda şu sonuçları görüntüler:


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

Sunucu tarafında şu günlük kayıtları görünür:


---nouvelle requête
04/07/19 13:36:05:789 : Autentification en cours…
04/07/19 13:36:05:789 : {"réponse":{"erreur":"Echec de l'authentification [x, x]"}}

19.3.5. Test 5

İstemci yapılandırma dosyasına doğru kullanıcı adı olan [admin, admin]'i tekrar girelim.

Şimdi, herhangi bir parametre belirtmeden bir tarayıcıdan doğrudan sunucuya URL [http://localhost/php7/scripts-web/impots/version-08/impots-server.php] adresini gidelim:

Sunucunun [logs.txt] günlük dosyasında şu satırlar yer almaktadır:


---nouvelle requête
04/07/19 13:37:33:711 : Autentification en cours…
04/07/19 13:37:33:711 : Authentification réussie [admin, admin]
04/07/19 13:37:33:711 : {"réponse":{"erreurs":["Méthode GET requise avec les seuls paramètres [marié, enfants, salaire]","paramètre marié manquant","paramètre enfants manquant","paramètre salaire manquant"]}}

19.4. [Codeception] Testleri

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

Image

19.4.0.1. [métier] katman testi

[ClientMetierTest.php] testi şu şekildedir:


<?php

// fonksiyon parametrelerinin beyan edilen türlerine sıkı bir şekilde uyuluyor
declare (strict_types=1);

// ad alanı
namespace Application;

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

// 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";
}
//
// kullanımlar
use Codeception\Test\Unit;
use const CONFIG_FILENAME;
use const ROOT;

// test sınıfı
class ClientMetierTest extends Unit {
  
}

Yorumlar

  • 08 sürümündeki test sınıfına kıyasla, yalnızca test edilecek istemcinin kök klasörünü belirten 10. satır değişmiştir;

Test sonuçları şöyledir:

Image

[logs.txt] sunucusunun günlüklerine bakmak ilginç olabilir:


04/07/19 13:48:48:525 :
---nouvelle requête
04/07/19 13:48:48:536 : Autentification en cours…
04/07/19 13:48:48:536 : Authentification réussie [admin, admin]
04/07/19 13:48:48:536 : paramètres ['marié'=>oui, 'enfants'=>2, 'salaire'=>55555] valides
04/07/19 13:48:48:536 : données fiscales prises en base de données
04/07/19 13:48:48:548 : {"réponse":{"impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}}
04/07/19 13:48:48:635 :
---nouvelle requête
04/07/19 13:48:48:645 : Autentification en cours…
04/07/19 13:48:48:645 : Authentification réussie [admin, admin]
04/07/19 13:48:48:645 : paramètres ['marié'=>oui, 'enfants'=>2, 'salaire'=>50000] valides
04/07/19 13:48:48:645 : données fiscales prises en base de données
04/07/19 13:48:48:655 : {"réponse":{"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}}
04/07/19 13:48:48:751 :
---nouvelle requête
04/07/19 13:48:48:762 : Autentification en cours…
04/07/19 13:48:48:762 : Authentification réussie [admin, admin]
04/07/19 13:48:48:762 : paramètres ['marié'=>oui, 'enfants'=>3, 'salaire'=>50000] valides
04/07/19 13:48:48:762 : données fiscales prises en base de données
04/07/19 13:48:48:773 : {"réponse":{"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}}
04/07/19 13:48:48:865 :
---nouvelle requête

---nouvelle requête
04/07/19 13:48:49:546 : Autentification en cours…
04/07/19 13:48:49:546 : Authentification réussie [admin, admin]
04/07/19 13:48:49:546 : paramètres ['marié'=>oui, 'enfants'=>3, 'salaire'=>200000] valides
04/07/19 13:48:49:546 : données fiscales prises en base de données
04/07/19 13:48:49:551 : {"réponse":{"impôt":42842,"surcôte":17283,"décôte":0,"réduction":0,"taux":0.41}}

Vergi idaresine ait verilerin her zaman veritabanından alındığı ve hiçbir zaman oturumdan alınmadığı görülmektedir. Yürütülen test koduna geri dönelim:


<?php

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

// ad alanı
namespace Application;



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

  public function __construct() {
    parent::__construct();
    // yapılandırmanın alınması
    $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 test2() {

  }

  public function test3() {

  }



}

[Codeception] test sınıfında, her test için oluşturucu çalıştırılıyor.

  • 21. satır: Dolayısıyla her test için [ClientDao] adlı yeni bir test sınıfı, NULL oturum çerezi ile birlikte oluşturulur. Bu durum, söz konusu müşterinin herhangi bir oturumdan yararlanamadığını açıklar;

Bu örnek, oturumun vergi idaresi verilerini depolamak için uygun bir yer olmadığını göstermektedir. Zira bu veriler, uygulamanın tüm kullanıcıları tarafından ortak olarak kullanılmaktadır. Oysa burada, bu veriler her bir kullanıcının oturumunda ayrı ayrı kopyalanmaktadır.

Web programlamada, paylaşılan veriler için üç tür görünürlük ayırt edilir:

  • web uygulamasının tüm kullanıcıları tarafından paylaşılan veriler. Bunlar genellikle salt okunur verilerdir. PHP, bu belleğe doğal olarak sahip değildir;
  • aynı müşterinin istekleri tarafından paylaşılan veriler. Bu veriler oturumda saklanır. Müşterinin belleğini ifade etmek için "müşteri oturumu" terimi kullanılır. Bir müşterinin tüm istekleri bu oturuma erişebilir. Bu oturumda bilgi depolayabilir ve okuyabilirler. Önceki betiklerde bu oturum, Symfony [HttpFoundation\Session\Session] nesnesi tarafından uygulanmaktadır;
  • istek belleği veya istek bağlamı olarak adlandırılır. Bir kullanıcının isteği, birbirini izleyen birden fazla eylem tarafından işlenebilir. İstek bağlamı, 1 numaralı eylemin 2 numaralı eyleme bilgi aktarmasını sağlar. Önceki kod parçacıklarında, istek Symfony [HttpFoundation\Request] nesnesi ile, istek belleği ise [HttpFoundation\Request::attributes] özniteliği ile uygulanmaktadır;

Image

PHP'e bir uygulama belleği sağlamak için üçüncü taraf kütüphaneler mevcuttur. Uygulama alıştırmasının yeni sürümü, bunlardan birinin kullanımını göstermektedir.