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:

- [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:

- [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.

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

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

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ı

[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ı


[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ı


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:

- [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;

- [6]'te devam ediyoruz;

- [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;

- [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:

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:

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:

[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

[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:

18.2.5.2. [métier] katman testleri

[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:

18.3. İstemci
Uygulamanın istemci tarafıyla ilgileniyoruz.

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

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

Yukarıdaki varlıkların tümü açıklanmış ve halihazırda kullanılmıştır:
- [BaseEntity], "bağlantı" paragrafında;
- [ExceptionImpots], “bağlantı” paragrafında;
- [TaxPayerData], "bağlantı" paragrafında;
18.3.2. [dao] katmanı

[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ı


[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ı


[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:

İ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:
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.

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:
