14. Vergi hesaplama hizmetinin HTTP JavaScript istemcileri
14.1. Introduction
Burada, vergi hesaplama hizmetinin 14. sürümüne ait bir [node.js] istemcisi yazmayı öneriyoruz. İstemci/sunucu mimarisi şu şekilde olacaktır:

İki farklı istemci sürümünü inceleyeceğiz:
- İstemcinin 1. sürümü, aşağıdaki katmanlara sahip [main, dao] yapısına sahip olacaktır:

- İstemcinin 2. sürümü, [main, métier, dao] yapısına sahip olacaktır. Sunucunun [métier] katmanı, istemciye taşınacaktır:

14.2. İstemci HTTP 1

Daha önce de belirttiğimiz gibi, HTTP 1 istemcisi aşağıdaki istemci/sunucu mimarisini uygular:

Şunları uygulayacağız:
- [dao] katmanını bir sınıf şeklinde;
- [main] katmanını bu sınıfı kullanan bir komut dosyası olarak;
14.2.1. [dao] katmanı
[dao] katmanı, aşağıdaki [Dao1.js] sınıfı ile uygulanacaktır:
''use strict';
// ithalatlar
import qs from 'qs'
class Dao1 {
// yapıcı
constructor(axios) {
// istekleri yapmak için axios kütüphanesi HTTP
this.axios = axios;
// oturum çerezi
this.sessionCookieName = "PHPSESSID";
this.sessionCookie = '';
}
// oturum başlatma
async initSession() {
// istek seçenekleri HHTP [get /main.php?action=init-session&type=json]
const options = {
method: "GET",
// URL parametreleri
params: {
action: 'init-session',
type: 'json'
}
};
// HTTP sorgusunun yürütülmesi
return await this.getRemoteData(options);
}
async authentifierUtilisateur(user, password) {
// HHTP isteğinin seçenekleri [post /main.php?action=authentifier-utilisateur]
const options = {
method: "POST",
headers: {
'Content-type': 'application/x-www-form-urlencoded',
},
// POST'in gövdesi
data: qs.stringify({
user: user,
password: password
}),
// URL'in parametreleri
params: {
action: 'authentifier-utilisateur'
}
};
// HTTP isteğinin yürütülmesi
return await this.getRemoteData(options);
}
// vergi hesaplaması
async calculerImpot(marié, enfants, salaire) {
// HHTP sorgusunun seçenekleri [post /main.php?action=calculer-impot]
const options = {
method: "POST",
headers: {
'Content-type': 'application/x-www-form-urlencoded',
},
// POST [marié, enfants, salaire]
data: qs.stringify({
marié: marié,
enfants: enfants,
salaire: salaire
}),
// URL parametreleri
params: {
action: 'calculer-impot'
}
};
// HTTP sorgusunun yürütülmesi
const data = await this.getRemoteData(options);
// sonuç
return data;
}
// simülasyon listesi
async listeSimulations() {
// HHTP sorgusunun seçenekleri [get /main.php?action=lister-simulations]
const options = {
method: "GET",
// URL parametreleri
params: {
action: 'lister-simulations'
},
};
// HTTP sorgusunun yürütülmesi
const data = await this.getRemoteData(options);
// sonuç
return data;
}
// simülasyon listesi
async supprimerSimulation(index) {
// sorgu seçenekleri HHTP [get /main.php?action=supprimer-simulation&numéro=index]
const options = {
method: "GET",
// URL parametreleri
params: {
action: 'supprimer-simulation',
numéro: index
},
};
// isteğin yürütülmesi HTTP
const data = await this.getRemoteData(options);
// sonuç
return data;
}
async getRemoteData(options) {
// oturum çerezi için
if (!options.headers) {
options.headers = {};
}
options.headers.Cookie = this.sessionCookie;
// HTTP isteğinin yürütülmesi
let response;
try {
// asenkron istek
response = await this.axios.request('main.php', options);
} catch (error) {
// [error] parametresi bir istisna örneğidir - çeşitli biçimlerde olabilir
if (error.response) {
// sunucunun yanıtı [error.response]'tedir
response = error.response;
} else {
// hata yeniden gönderiliyor
throw error;
}
}
// yanıt, sunucudan gelen HTTP yanıtının tamamıdır (HTTP başlıkları + yanıtın kendisi)
// varsa oturum çerezi alınır
const setCookie = response.headers['set-cookie'];
if (setCookie) {
// setCookie bir dizidir
// bu dizide oturum çerezi aranır
let trouvé = false;
let i = 0;
while (!trouvé && i < setCookie.length) {
// oturum çerezi aranır
const results = RegExp('^(' + this.sessionCookieName + '.+?);').exec(setCookie[i]);
if (results) {
// oturum çerezi kaydedilir
// eslint-disable-next-line require-atomic-updates
this.sessionCookie = results[1];
// bulundu
trouvé = true;
} else {
// sonraki öğe
i++;
}
}
}
// sunucu yanıtı şurada: [response.data]
return response.data;
}
}
// sınıfın dışa aktarımı
export default Dao1;
- Burada, bağlantı paragrafında öğrendiklerimizi kullanıyoruz; o paragrafta, hem [node.js] altında hem de bir tarayıcıda HTTP istekleri yapmayı sağlayan [axios] kütüphanesini tanıtmıştık. Özellikle bağlantı paragrafındaki komut dosyasına bakacağız;
- 9-15. satırlar: sınıf oluşturucu. Bu sınıfın üç özelliği olacaktır:
- [axios]: [axios] isteklerini gerçekleştirmemizi sağlayan HTTP nesnesi. Bu nesne, çağıran kod tarafından aktarılır;
- [sessionCookieName]: Sunuculara göre oturum çerezi farklı isimler alabilir. Burada adı [PHPSESSID]'tir;
- [sessionCookie]: sunucu tarafından gönderilen ve istemci tarafından saklanan oturum çerezi;
- 53-76. satırlar: Asenkron [calculerImpot] işlevi, [marié, enfants, salaire] parametrelerini göndererek [post /main.php?action=calculer-impot] isteğini gerçekleştirir. Bu işlev, sunucu tarafından iletilen jSON dizesini bir JavaScript nesnesi biçiminde döndürür;
- 79-92. satırlar: [listeSimulations] asenkron işlevi, [$status, $état, [‘réponse’=>$response]] isteğini gönderir. Sunucu tarafından iletilen jSON dizesini bir JavaScript nesnesi biçiminde döndürür;
- 95-109. satırlar: Asenkron [supprimerSimulation] işlevi, [get /main.php?action=supprimer-simulation&numéro=index] isteğini gerçekleştirir. Sunucu tarafından iletilen jSON dizesini bir JavaScript nesnesi biçiminde döndürür;
- 121. satır: Burada [this.axios] notasyonu kullanılır, çünkü burada oluşturucuya iletilen [axios] nesnesi, [this.axios] özelliğinde saklanmıştır;
- 161. satır: [Dao1] sınıfı, kullanılabilmesi için dışa aktarılır;
14.2.2. [main1.js] komut dosyası
[main1.js] komut dosyası, [Dao1] sınıfını kullanarak sunucuya bir dizi çağrı yapar:
- jSON oturumunun başlatılması;
- [admin, admin] ile kimlik doğrulama;
- üç vergi hesaplaması talep eder;
- simülasyon listesini ister;
- bunlardan birini siler;
Kod şu şekildedir:
// axios'un içe aktarılması
import axios from 'axios';
// Dao1 sınıfının içe aktarılması
import Dao from './Dao1';
// asenkron işlev [main]
async function main() {
// axios yapılandırması
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';
// katman örneği oluşturma [dao]
const dao = new Dao(axios);
// [dao] katmanının kullanımı
try {
// oturum başlatma
log("-----------init-session");
let response = await dao.initSession();
log(response);
// kimlik doğrulama
log("-----------authentifier-utilisateur");
response = await dao.authentifierUtilisateur("admin", "admin");
log(response);
// vergi hesaplamaları
log("-----------calculer-impot x 3");
response = await Promise.all([
dao.calculerImpot("oui", 2, 45000),
dao.calculerImpot("non", 2, 45000),
dao.calculerImpot("non", 1, 30000)
]);
log(response);
// simülasyon listesi
log("-----------liste-des-simulations");
response = await dao.listeSimulations();
log(response);
// simülasyon silme
log("-----------suppression simulation n° 1");
response = await dao.supprimerSimulation(1);
log(response);
} catch (error) {
// hatayı günlüğe kaydetme
console.log("erreur=", error.message);
}
}
// jSON günlüğü
function log(object) {
console.log(JSON.stringify(object, null, 2));
}
// çalıştırma
main();
Açıklamalar
- 2. satır: [axios] kütüphanesini içe aktarıyoruz;
- 4. satır: [Dao] sınıfı içe aktarılır;
- 7. satır: Sunucuyla iletişim kuran [main] işlevi asenkron;
- 9-10. satırlar: sunucuya gönderilecek HTTP isteklerinin varsayılan yapılandırması:
- 9. satır: 2 saniyelik [timeout];
- 10. satır: Tüm URL isteklerinin önekinde, vergi hesaplama sunucusunun 14. sürümüne ait temel URL bulunur;
- 12. satır: [Dao] katmanı oluşturulur. Artık bu katman kullanılabilir;
- satır 46-48: [log] işlevinin amacı, bir JavaScript nesnesinin jSON dizesini süslenmiş bir biçimde görüntülemektir: iki boşluk girintili dikey biçimde (3. parametre);
- satır 15-18: jSON oturumunun başlatılması;
- satır 19-22: kimlik doğrulama;
- satır 23-30: üç vergi hesaplaması aynı anda talep edilir. [await Promise.all] sayesinde, üç sonuç da elde edilene kadar işlem askıya alınır;
- satır 31-34: simülasyon listesi;
- satır 35-38: bir simülasyonun silinmesi;
- satır 39-42: olası istisnaların yönetimi;
Yürütme sonuçları şunlardır:
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\client impôts\client http 1\main1.js"
"-----------init-session"
{
"action": "init-session",
"état": 700,
"réponse": "session démarrée avec type [json]"
}
"-----------authentifier-utilisateur"
{
"action": "authentifier-utilisateur",
"état": 200,
"réponse": "Authentification réussie [admin, admin]"
}
"-----------calculer-impot x 3"
[
{
"action": "calculer-impot",
"état": 300,
"réponse": {
"marié": "oui",
"enfants": "2",
"salaire": "45000",
"impôt": 502,
"surcôte": 0,
"décôte": 857,
"réduction": 126,
"taux": 0.14
}
},
{
"action": "calculer-impot",
"état": 300,
"réponse": {
"marié": "non",
"enfants": "2",
"salaire": "45000",
"impôt": 3250,
"surcôte": 370,
"décôte": 0,
"réduction": 0,
"taux": 0.3
}
},
{
"action": "calculer-impot",
"état": 300,
"réponse": {
"marié": "non",
"enfants": "1",
"salaire": "30000",
"impôt": 1687,
"surcôte": 0,
"décôte": 0,
"réduction": 0,
"taux": 0.14
}
}
]
"-----------liste-des-simulations"
{
"action": "lister-simulations",
"état": 500,
"réponse": [
{
"marié": "oui",
"enfants": "2",
"salaire": "45000",
"impôt": 502,
"surcôte": 0,
"décôte": 857,
"réduction": 126,
"taux": 0.14,
"arrayOfAttributes": null
},
{
"marié": "non",
"enfants": "2",
"salaire": "45000",
"impôt": 3250,
"surcôte": 370,
"décôte": 0,
"réduction": 0,
"taux": 0.3,
"arrayOfAttributes": null
},
{
"marié": "non",
"enfants": "1",
"salaire": "30000",
"impôt": 1687,
"surcôte": 0,
"décôte": 0,
"réduction": 0,
"taux": 0.14,
"arrayOfAttributes": null
}
]
}
"-----------suppression simulation n° 1"
{
"action": "supprimer-simulation",
"état": 600,
"réponse": [
{
"marié": "oui",
"enfants": "2",
"salaire": "45000",
"impôt": 502,
"surcôte": 0,
"décôte": 857,
"réduction": 126,
"taux": 0.14,
"arrayOfAttributes": null
},
{
"marié": "non",
"enfants": "1",
"salaire": "30000",
"impôt": 1687,
"surcôte": 0,
"décôte": 0,
"réduction": 0,
"taux": 0.14,
"arrayOfAttributes": null
}
]
}
[Done] exited with code=0 in 0.516 seconds
14.3. Müşteri HTTP 2

HTTP2 istemcisinin mimarisi şöyledir:

[métier] katmanı sunucudan JavaScript istemcisine taşınmıştır. PHP7 dersinde yaptığımızın aksine, burada [main] katmanı, [dao] katmanına ulaşmak için [métier] katmanından geçmek zorunda kalmayacaktır. Bu iki katmanı yetkinlik merkezleri olarak kullanacağız:
- [main] katmanı, sunucuda bulunan verilere ihtiyaç duyduğu anda [dao] katmanından geçer;
- [main] katmanı, vergi hesaplamalarını yapması için [métier] katmanına talepte bulunur;
- [métier] katmanı, [dao] katmanından bağımsızdır ve ona hiçbir zaman başvurmaz;
14.3.1. [Métier] JavaScript sınıfı
[Métier] sınıfının PHP'teki özü, bağlantıdaki makalede açıklanmıştır. Bu oldukça karmaşık bir koddur; burada onu açıklamak için değil, JavaScript'e çevirebilmek için tekrar ediyoruz:
<?php
// ad alanı
namespace Application;
class Metier implements InterfaceMetier {
// Dao katmanı
private $dao;
// vergi idaresi verileri
private $taxAdminData;
//---------------------------------------------
// katman ayarlayıcısı [dao]
public function setDao(InterfaceDao $dao) {
$this->dao = $dao;
return $this;
}
public function __construct(InterfaceDao $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 {
// $marié: evet, hayır
// $enfants: çocuk sayısı
// $salaire: yıllık maaş
// $this->taxAdminData: vergi dairesi verileri
//
// Vergi dairesi verilerinin mevcut olup olmadığı kontrol edilir
if ($this->taxAdminData === NULL) {
$this->taxAdminData = $this->getTaxAdminData();
}
// çocuklu vergi hesaplaması
$result1 = $this->calculerImpot2($marié, $enfants, $salaire);
$impot1 = $result1["impôt"];
// çocuklar hariç vergi hesaplaması
if ($enfants != 0) {
$result2 = $this->calculerImpot2($marié, 0, $salaire);
$impot2 = $result2["impôt"];
// aile katsayısı üst sınırının uygulanması
$plafonDemiPart = $this->taxAdminData->getPlafondQfDemiPart();
if ($enfants < 3) {
// $PLAFOND_QF_DEMI_PART avro (ilk 2 çocuk için)
$impot2 = $impot2 - $enfants * $plafonDemiPart;
} else {
// $PLAFOND_QF_DEMI_PART avro ilk 2 çocuk için, sonraki çocuklar için iki katı
$impot2 = $impot2 - 2 * $plafonDemiPart - ($enfants - 2) * 2 * $plafonDemiPart;
}
} else {
$impot2 = $impot1;
$result2 = $result1;
}
// en yüksek vergi oranı alınır
if ($impot1 > $impot2) {
$impot = $impot1;
$taux = $result1["taux"];
$surcôte = $result1["surcôte"];
} else {
$surcôte = $impot2 - $impot1 + $result2["surcôte"];
$impot = $impot2;
$taux = $result2["taux"];
}
// olası bir indirimin hesaplanması
$décôte = $this->getDecôte($marié, $salaire, $impot);
$impot -= $décôte;
// olası vergi indiriminin hesaplanması
$réduction = $this->getRéduction($marié, $salaire, $enfants, $impot);
$impot -= $réduction;
// 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 {
// $marié: evet, hayır
// $enfants: çocuk sayısı
// $salaire : yıllık maaş
// $this->taxAdminData : vergi dairesi verileri
//
// pay sayısı
$marié = strtolower($marié);
if ($marié === "oui") {
$nbParts = $enfants / 2 + 2;
} else {
$nbParts = $enfants / 2 + 1;
}
// 3. çocuktan itibaren her çocuk için 1 pay
if ($enfants >= 3) {
// 3. çocuktan itibaren her çocuk için yarım pay daha
$nbParts += 0.5 * ($enfants - 2);
}
// vergilendirilebilir gelir
$revenuImposable = $this->getRevenuImposable($salaire);
// ek ücret
$surcôte = floor($revenuImposable - 0.9 * $salaire);
// yuvarlama hataları için
if ($surcôte < 0) {
$surcôte = 0;
}
// aile katsayısı
$quotient = $revenuImposable / $nbParts;
// vergi hesaplaması
$limites = $this->taxAdminData->getLimites();
$coeffR = $this->taxAdminData->getCoeffR();
$coeffN = $this->taxAdminData->getCoeffN();
// takip eden döngüyü durdurmak için sınırlar tablosunun sonuna eklenir
$limites[count($limites) - 1] = $quotient;
// vergi oranının aranması
$i = 0;
while ($quotient > $limites[$i]) {
$i++;
}
// $quotient'in $limites tablosunun sonuna yerleştirilmesi nedeniyle, önceki döngü
// $limites tablosunun sınırlarını aşamaz
// şimdi vergiyi hesaplayabiliriz
$impôt = floor($revenuImposable * $coeffR[$i] - $nbParts * $coeffN[$i]);
// sonuç
return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
// revenuImposable=yıllıkMaaş-muafiyet
// muafiyetin bir alt ve üst sınırı vardır
private function getRevenuImposable(float $salaire): float {
// maaşın %10'u kadar indirim
$abattement = 0.1 * $salaire;
// bu indirim $this->taxAdminData değerini aşamaz->getAbattementDixPourCentMax()
if ($abattement > $this->taxAdminData->getAbattementDixPourCentMax()) {
$abattement = $this->taxAdminData->getAbattementDixPourcentMax();
}
// indirim tutarı $this->taxAdminData değerinden az olamaz->getAbattementDixPourcentMin()
if ($abattement < $this->taxAdminData->getAbattementDixPourcentMin()) {
$abattement = $this->taxAdminData->getAbattementDixPourcentMin();
}
// vergilendirilebilir gelir
$revenuImposable = $salaire - $abattement;
// sonuç
return floor($revenuImposable);
}
// olası bir iskontoyu hesaplar
private function getDecôte(string $marié, float $salaire, float $impots): float {
// başlangıçta indirim sıfırdır
$décôte = 0;
// indirimden yararlanmak için maksimum vergi tutarı
$plafondImpôtPourDécôte = $marié === "oui" ?
$this->taxAdminData->getPlafondImpotCouplePourDecote() :
$this->taxAdminData->getPlafondImpotCelibatairePourDecote();
if ($impots < $plafondImpôtPourDécôte) {
// indirim tutarının üst sınırı
$plafondDécôte = $marié === "oui" ?
$this->taxAdminData->getPlafondDecoteCouple() :
$this->taxAdminData->getPlafondDecoteCelibataire();
// teorik indirim
$décôte = $plafondDécôte - 0.75 * $impots;
// indirim, vergi tutarını aşamaz
if ($décôte > $impots) {
$décôte = $impots;
}
// <0 indirim uygulanmaz
if ($décôte < 0) {
$décôte = 0;
}
}
// sonuç
return ceil($décôte);
}
// olası bir indirimi hesaplar
private function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
// %20 indirimden yararlanabilmek için gelir üst sınırı
$plafondRevenuPourRéduction = $marié === "oui" ?
$this->taxAdminData->getPlafondRevenusCouplePourReduction() :
$this->taxAdminData->getPlafondRevenusCelibatairePourReduction();
$plafondRevenuPourRéduction += $enfants * $this->taxAdminData->getValeurReducDemiPart();
if ($enfants > 2) {
$plafondRevenuPourRéduction += ($enfants - 2) * $this->taxAdminData->getValeurReducDemiPart();
}
// vergilendirilebilir gelir
$revenuImposable = $this->getRevenuImposable($salaire);
// indirim
$réduction = 0;
if ($revenuImposable < $plafondRevenuPourRéduction) {
// %20 indirim
$réduction = 0.2 * $impots;
}
// sonuç
return ceil($réduction);
}
// toplu iş modunda vergi hesaplaması
public function executeBatchImpots(string $taxPayersFileName, string $resultsFileName, string $errorsFileName): void {
// [dao] katmanından gelen istisnaların yukarıya aktarılmasına izin verilir
// vergi mükellefi verileri alınır
$taxPayersData = $this->dao->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->setMontant($result["impôt"]);
$taxPayerData->setDécôte($result["décôte"]);
$taxPayerData->setSurCôte($result["surcôte"]);
$taxPayerData->setTaux($result["taux"]);
$taxPayerData->setRéduction($result["réduction"]);
// sonuç sonuç tablosuna ekleniyor
$results [] = $taxPayerData;
}
// sonuçların kaydedilmesi
$this->dao->saveResults($resultsFileName, $results);
}
}
- 19-26. satırlar: PHP sınıfının oluşturucusu. [dao] katmanından bağımsız bir [métier] katmanı oluşturacağımızı söylediğimiz için, bu oluşturucuda JavaScript ile iki değişiklik yapacağız:
- [dao] katmanından bir örnek almayacak (artık buna ihtiyacı yok);
- [dao] katmanından [taxAdminData] idaresine ait vergi verilerini talep etmeyecek: bu veriyi oluşturucuya ileten, çağıran kod olacaktır;
- 197-122. satırlar: Simülasyon sonuçlarını bir metin dosyasına kaydetmeyi amaçlayan [executeBatchImpots] yöntemini uygulamayacağız. Hem [node.js] altında hem de bir tarayıcıda çalışan bir kod istiyoruz. Ancak, istemci tarayıcısını çalıştıran makinenin dosya sistemine veri kaydetmek mümkün değildir;
Bu kısıtlamalar göz önünde bulundurularak, [Métier] JavaScript sınıfının kodu şu şekildedir:
'use strict';
// Métier sınıfı
class Métier {
// yapıcı
constructor(taxAdmindata) {
// this.taxAdminData: vergi idaresi verileri
this.taxAdminData = taxAdmindata;
}
// vergi hesaplaması
// --------------------------------------------------------------------------
calculerImpot(marié, enfants, salaire) {
// evli: evet, hayır
// çocuklar: çocuk sayısı
// maaş: yıllık maaş
// this.taxAdminData: vergi dairesi verileri
//
// çocuklu vergi hesaplaması
const result1 = this.calculerImpot2(marié, enfants, salaire);
const impot1 = result1["impôt"];
// çocuklar hariç vergi hesaplaması
let result2, impot2, plafondDemiPart;
if (enfants !== 0) {
result2 = this.calculerImpot2(marié, 0, salaire);
impot2 = result2["impôt"];
// aile katsayısı üst sınırının uygulanması
plafondDemiPart = this.taxAdminData.plafondQfDemiPart;
if (enfants < 3) {
// PLAFOND_QF_DEMI_PART avro (ilk 2 çocuk için)
impot2 = impot2 - enfants * plafondDemiPart;
} else {
// PLAFOND_QF_DEMI_PART avro ilk 2 çocuk için, sonraki çocuklar için iki katı
impot2 = impot2 - 2 * plafondDemiPart - (enfants - 2) * 2 * plafondDemiPart;
}
} else {
// vergi yeniden hesaplanmaz
impot2 = impot1;
result2 = result1;
}
// [impot1, impot2]'teki en yüksek vergi alınır
let impot, taux, surcôte;
if (impot1 > impot2) {
impot = impot1;
taux = result1["taux"];
surcôte = result1["surcôte"];
} else {
surcôte = impot2 - impot1 + result2["surcôte"];
impot = impot2;
taux = result2["taux"];
}
// olası indirim hesaplanır
const décôte = this.getDecôte(marié, impot);
impot -= décôte;
// olası vergi indiriminin hesaplanması
const réduction = this.getRéduction(marié, salaire, enfants, impot);
impot -= réduction;
// sonuç
return {
"impôt": Math.floor(impot), "surcôte": surcôte, "décôte": décôte, "réduction": réduction,
"taux": taux
};
}
// --------------------------------------------------------------------------
calculerImpot2(marié, enfants, salaire) {
// evli: evet, hayır
// çocuklar: çocuk sayısı
// maaş: yıllık maaş
// this->taxAdminData : vergi dairesi verileri
//
// pay sayısı
marié = marié.toLowerCase();
let nbParts;
if (marié === "oui") {
nbParts = enfants / 2 + 2;
} else {
nbParts = enfants / 2 + 1;
}
// 3. çocuktan itibaren her çocuk için 1 pay
if (enfants >= 3) {
// 3. çocuktan itibaren her çocuk için yarım pay daha
nbParts += 0.5 * (enfants - 2);
}
// vergilendirilebilir gelir
const revenuImposable = this.getRevenuImposable(salaire);
// ek ücret
let surcôte = Math.floor(revenuImposable - 0.9 * salaire);
// yuvarlama sorunları için
if (surcôte < 0) {
surcôte = 0;
}
// aile gelir katsayısı
const quotient = revenuImposable / nbParts;
// vergi hesaplaması
const limites = this.taxAdminData.limites;
const coeffR = this.taxAdminData.coeffR;
const coeffN = this.taxAdminData.coeffN;
// takip eden döngüyü durdurmak için sınırlar tablosunun sonuna eklenir
limites[limites.length - 1] = quotient;
// vergi oranının aranması
let i = 0;
while (quotient > limites[i]) {
i++;
}
// aile katsayısı sınır tablosunun sonuna yerleştirildiği için, önceki döngü
// sınır tablosunun sınırlarını aşamaz
// artık vergiyi hesaplayabiliriz
const impôt = Math.floor(revenuImposable * coeffR[i] - nbParts * coeffN[i]);
// sonuç
return { "impôt": impôt, "surcôte": surcôte, "taux": coeffR[i] };
}
// revenuImposable=yıllıkMaaş-muafiyet
// muafiyetin bir alt ve üst sınırı vardır
getRevenuImposable(salaire) {
// maaşın %10'u kadar indirim
let abattement = 0.1 * salaire;
// bu indirim taxAdminData.getAbattementDixPourCentMax() değerini aşamaz
if (abattement > this.taxAdminData.abattementDixPourCentMax) {
abattement = this.taxAdminData.abattementDixPourcentMax;
}
// indirim, taxAdminData.getAbattementDixPourcentMin() değerinden az olamaz
if (abattement < this.taxAdminData.abattementDixPourcentMin) {
abattement = this.taxAdminData.abattementDixPourcentMin;
}
// vergilendirilebilir gelir
const revenuImposable = salaire - abattement;
// sonuç
return Math.floor(revenuImposable);
}
// olası bir iskonto hesaplar
getDecôte(marié, impots) {
// başlangıçta indirim sıfırdır
let décôte = 0;
// indirimden yararlanmak için maksimum vergi tutarı
let plafondImpôtPourDécôte = marié === "oui" ?
this.taxAdminData.plafondImpotCouplePourDecote :
this.taxAdminData.plafondImpotCelibatairePourDecote;
let plafondDécôte;
if (impots < plafondImpôtPourDécôte) {
// indirim tutarının üst sınırı
plafondDécôte = marié === "oui" ?
this.taxAdminData.plafondDecoteCouple :
this.taxAdminData.plafondDecoteCelibataire;
// teorik indirim
décôte = plafondDécôte - 0.75 * impots;
// indirim, vergi tutarını aşamaz
if (décôte > impots) {
décôte = impots;
}
// <0 indirim yok
if (décôte < 0) {
décôte = 0;
}
}
// sonuç
return Math.ceil(décôte);
}
// olası bir indirimi hesaplar
getRéduction(marié, salaire, enfants, impots) {
// %20 indirimden yararlanabilmek için gelir üst sınırı
let plafondRevenuPourRéduction = marié === "oui" ?
this.taxAdminData.plafondRevenusCouplePourReduction :
this.taxAdminData.plafondRevenusCelibatairePourReduction;
plafondRevenuPourRéduction += enfants * this.taxAdminData.valeurReducDemiPart;
if (enfants > 2) {
plafondRevenuPourRéduction += (enfants - 2) * this.taxAdminData.valeurReducDemiPart;
}
// vergilendirilebilir gelir
const revenuImposable = this.getRevenuImposable(salaire);
// indirim
let réduction = 0;
if (revenuImposable < plafondRevenuPourRéduction) {
// %20 indirim
réduction = 0.2 * impots;
}
// sonuç
return Math.ceil(réduction);
}
}
// sınıfın dışa aktarımı
export default Métier;
- Javascript kodu, PHP kodunu harfiyen takip eder;
- [Métier] sınıfı dışa aktarılıyor, 187. satır;
14.3.2. Javascript sınıfı [Dao2]

[Dao2] sınıfı, yukarıdaki JavaScript istemcisinin [dao] katmanını şu şekilde uygular:
'use strict';
// ithalatlar
import qs from 'qs'
class Dao2 {
// yapıcı
constructor(axios) {
this.axios = axios;
// oturum çerezi
this.sessionCookieName = "PHPSESSID";
this.sessionCookie = '';
}
// oturumu başlat
async initSession() {
// istek seçenekleri HHTP [get /main.php?action=init-session&type=json]
const options = {
method: "GET",
// URL parametreleri
params: {
action: 'init-session',
type: 'json'
}
};
// HTTP sorgusunun yürütülmesi
return await this.getRemoteData(options);
}
async authentifierUtilisateur(user, password) {
// HHTP isteğinin seçenekleri [post /main.php?action=authentifier-utilisateur]
const options = {
method: "POST",
headers: {
'Content-type': 'application/x-www-form-urlencoded',
},
// POST'in gövdesi
data: qs.stringify({
user: user,
password: password
}),
// URL parametreleri
params: {
action: 'authentifier-utilisateur'
}
};
// HTTP isteğinin yürütülmesi
return await this.getRemoteData(options);
}
async getAdminData() {
// HHTP sorgusunun seçenekleri [get /main.php?action=get-admindata]
const options = {
method: "GET",
// URL sorgusunun parametreleri
params: {
action: 'get-admindata'
}
};
// HTTP sorgusunun yürütülmesi
const data = await this.getRemoteData(options);
// sonuç
return data;
}
async getRemoteData(options) {
// oturum çerezi için
if (!options.headers) {
options.headers = {};
}
options.headers.Cookie = this.sessionCookie;
// HTTP isteğinin yürütülmesi
let response;
try {
// asenkron istek
response = await this.axios.request('main.php', options);
} catch (error) {
// [error] parametresi bir istisna örneğidir - çeşitli biçimlerde olabilir
if (error.response) {
// sunucunun yanıtı [error.response]'tedir
response = error.response;
} else {
// hata yeniden gönderiliyor
throw error;
}
}
// yanıt, sunucudan gelen HTTP yanıtının tamamıdır (HTTP başlıkları + yanıtın kendisi)
// varsa oturum çerezi alınır
const setCookie = response.headers['set-cookie'];
if (setCookie) {
// setCookie bir dizidir
// bu dizide oturum çerezi aranır
let trouvé = false;
let i = 0;
while (!trouvé && i < setCookie.length) {
// oturum çerezi aranır
const results = RegExp('^(' + this.sessionCookieName + '.+?);').exec(setCookie[i]);
if (results) {
// oturum çerezi kaydedilir
// eslint-disable-next-line require-atomic-updates
this.sessionCookie = results[1];
// bulundu
trouvé = true;
} else {
// sonraki öğe
i++;
}
}
}
// sunucu yanıtı şurada: [response.data]
return response.data;
}
}
// sınıfın dışa aktarımı
export default Dao2;
Açıklamalar
- [Dao2] sınıfı, vergi hesaplama sunucusuna gönderilebilecek isteklerden yalnızca üçünü uygular:
- [init-session] (satır 17-29): jSON oturumunu başlatmak için;
- [authentifier-utilisateur] (satır 31-50): kimlik doğrulaması yapmak için;
- [get-admindata] (satır 52-65): istemci tarafında vergi hesaplamalarını yapmamızı sağlayacak vergi idaresi verilerini almak için;
- satır 52-65: Sunucuya yönelik yeni bir [get-admindata] eylemi ekliyoruz. Bu eylem daha önce uygulanmamıştı. Şimdi bunu gerçekleştiriyoruz.
14.3.3. Vergi hesaplama sunucusunda değişiklik
Vergi hesaplama sunucusu yeni bir eylemi uygulamaya koymalıdır. Bunu sunucunun 14. sürümünde gerçekleştireceğiz. Uygulanacak eylemin özellikleri şunlardır:
- [get /main.php?action=get-admindata] işlemi tarafından talep edilir;
- Vergi idaresi verilerini kapsayan bir nesnenin jSON dizesini döndürür;
Sunucumuza bir eylem eklemeyi tekrar gözden geçireceğiz.
Değişiklik NetBeans ortamında yapılacaktır:

[2]'te, yeni eylemi eklemek için [config.json] dosyasını değiştiriyoruz:
{
"databaseFilename": "Config/database.json",
"corsAllowed": true,
"rootDirectory": "C:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-14",
"relativeDependencies": [
"/Entities/BaseEntity.php",
"/Entities/Simulation.php",
"/Entities/Database.php",
"/Entities/TaxAdminData.php",
"/Entities/ExceptionImpots.php",
"/Utilities/Logger.php",
"/Utilities/SendAdminMail.php",
"/Model/InterfaceServerDao.php",
"/Model/ServerDao.php",
"/Model/ServerDaoWithSession.php",
"/Model/InterfaceServerMetier.php",
"/Model/ServerMetier.php",
"/Responses/InterfaceResponse.php",
"/Responses/ParentResponse.php",
"/Responses/JsonResponse.php",
"/Responses/XmlResponse.php",
"/Responses/HtmlResponse.php",
"/Controllers/InterfaceController.php",
"/Controllers/InitSessionController.php",
"/Controllers/ListerSimulationsController.php",
"/Controllers/AuthentifierUtilisateurController.php",
"/Controllers/CalculerImpotController.php",
"/Controllers/SupprimerSimulationController.php",
"/Controllers/FinSessionController.php",
"/Controllers/AfficherCalculImpotController.php",
"/Controllers/AdminDataController.php"
],
"absoluteDependencies": [
"C:/myprograms/laragon-lite/www/vendor/autoload.php",
"C:/myprograms/laragon-lite/www/vendor/predis/predis/autoload.php"
],
"users": [
{
"login": "admin",
"passwd": "admin"
}
],
"adminMail": {
"smtp-server": "localhost",
"smtp-port": "25",
"from": "guest@localhost",
"to": "guest@localhost",
"subject": "plantage du serveur de calcul d'impôts",
"tls": "FALSE",
"attachments": []
},
"logsFilename": "Logs/logs.txt",
"actions":
{
"init-session": "\\InitSessionController",
"authentifier-utilisateur": "\\AuthentifierUtilisateurController",
"calculer-impot": "\\CalculerImpotController",
"lister-simulations": "\\ListerSimulationsController",
"supprimer-simulation": "\\SupprimerSimulationController",
"fin-session": "\\FinSessionController",
"afficher-calcul-impot": "\\AfficherCalculImpotController",
"get-admindata": "\\AdminDataController"
},
"types": {
"json": "\\JsonResponse",
"html": "\\HtmlResponse",
"xml": "\\XmlResponse"
},
"vues": {
"vue-authentification.php": [700, 221, 400],
"vue-calcul-impot.php": [200, 300, 341, 350, 800],
"vue-liste-simulations.php": [500, 600]
},
"vue-erreurs": "vue-erreurs.php"
}
Değişiklik şu şekilde yapılacaktır:
- 67. satır: [get-admindata] eylemini ekleyin ve bir denetleyiciyle ilişkilendirin;
- 36. satır: Bu denetleyiciyi, PHP uygulaması tarafından yüklenecek sınıflar listesinde tanımlayın;
Bir sonraki aşama, [AdminDataController] ve [3] denetleyicilerini uygulamaya koymaktır:
<?php
namespace Application;
// Symfony bağımlılıkları
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
// katman takma adı [dao]
use \Application\ServerDaoWithSession as ServerDaoWithRedis;
class AdminDataController implements InterfaceController {
// $config uygulama yapılandırmasıdır
// bir Request isteğinin işlenmesi
// Session oturumunu kullanır ve değiştirebilir
// $infos, her denetleyiciye özgü ek bilgilerdir
// bir dizi döndürür: [$statusCode, $état, $content, $headers]
public function execute(
array $config,
Request $request,
Session $session,
array $infos = NULL): array {
// tek bir parametre olmalıdır GET
$method = strtolower($request->getMethod());
$erreur = $method !== "get" || $request->query->count() != 1;
if ($erreur) {
// hata kaydedilir
$message = "il faut utiliser la méthode [get] avec l'unique paramètre [action] dans l'URL";
$état = 1001;
// sonuç ana denetleyiciye geri gönderilir
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// çalışmaya devam edilebilir
// Redis
\Predis\Autoloader::register();
try {
// [predis] istemcisi
$redis = new \Predis\Client();
// sunucuya bağlanarak sunucunun çalışıp çalışmadığını kontrol ediyoruz
$redis->connect();
} catch (\Predis\Connection\ConnectionException $ex) {
// işler ters gitti
// ana denetleyiciye hata içeren sonuç geri döndü
$état = 1050;
return [Response::HTTP_INTERNAL_SERVER_ERROR, $état,
["réponse" => "[redis], " . utf8_encode($ex->getMessage())], []];
}
// Vergi dairesinden verilerin alınması
// önce önbellekte aranıyor [redis]
if (!$redis->get("taxAdminData")) {
try {
// vergi verileri veritabanından alınacak
$dao = new ServerDaoWithRedis($config["databaseFilename"], NULL);
// taxAdminData
$taxAdminData = $dao->getTaxAdminData();
// Alınan veriler Redis'e kaydedilir
$redis->set("taxAdminData", $taxAdminData);
} catch (\RuntimeException $ex) {
// işler ters gitti
// Hata içeren sonuç ana denetleyiciye geri gönderiliyor
$état = 1041;
return [Response::HTTP_INTERNAL_SERVER_ERROR, $état,
["réponse" => utf8_encode($ex->getMessage())], []];
}
} else {
// Vergi verileri, [redis] kapsamındaki [application] belleğinden alınır
$arrayOfAttributes = \json_decode($redis->get("taxAdminData"), true);
// önceki öznitelik tablosundan bir [TaxAdminData] nesnesi oluşturuluyor
$taxAdminData = (new TaxAdminData())->setFromArrayOfAttributes($arrayOfAttributes);
}
// sonuç ana denetleyiciye geri gönderilir
$état = 1000;
return [Response::HTTP_OK, $état, ["réponse" => $taxAdminData], []];
}
}
Açıklamalar
- 12. satır: Sunucudaki diğer denetleyiciler gibi, [AdminDataController] de 19-79. satırlardaki [execute] yönteminden oluşan [InterfaceController] arayüzünü uygular;
- 78. satır: Sunucudaki diğer denetleyicilerde olduğu gibi, [AdminDataController.execute] yöntemi [$status, $état, [‘réponse’=>$response]] dizisini döndürür;
- [$status]: HTTP yanıtının durum kodu;
- [$état]: istemcinin isteğinin yürütülmesinden sonra sunucunun bulunduğu durumu temsil eden uygulama içi bir kod;
- [$response]: müşteriye gönderilecek yanıtı içeren bir dizi. Burada bu dizi daha sonra jSON dizesine dönüştürülecektir;
- 25-34. satırlar: müşterinin [get-admindata] eyleminin sözdizimsel olarak doğru olup olmadığı kontrol edilir;
- satır 37-74: Aşağıdaki kaynaklardan bulunan bir [TaxAdminData] nesnesi alınır:
- satır 56-59: [redis] önbelleğinde bulunamadığı takdirde veritabanından;
- 70-73. satırlar: [redis] önbelleğinde;
Bu kod, bağlantıdaki makalede açıklanan [CalculerImpotController] denetleyicisinin kodunu kullanır. Aslında, bu denetleyici de vergi idaresi verilerini kapsayan [TaxAdminData] nesnesini almalıydı.
Javascript istemcisi testleri sırasında, [TaxAdminData] nesnesinin jSON biçimi, bu nesne [redis] önbelleğinde bulunduğunda sorun yarattı. Bunu anlamak için, bu nesnenin [redis] içinde hangi biçimde depolandığını inceleyelim:


- [5-7] örneğinde, sayısal değerlerin karakter dizisi biçiminde depolandığı görülmektedir. PHP bu durumu sorun etmedi, çünkü sayılar ve karakter dizileri arasındaki hesaplamalarda + operatörü, karakter dizisinin türünü örtük olarak sayıya dönüştürür. Ancak JavaScript tam tersini yapar: Sayılar ve dizeler arasındaki hesaplamalarda + operatörü, sayının türünü örtük olarak bir karakter dizisine dönüştürür. Dolayısıyla, JavaScript sınıfı [Métier]’teki hesaplamalar hatalı olur;
Bu sorunu gidermek için, denetleyicinin 71. satırında kullanılan [TaxAdminData.setFromArrayOfAttributes] yöntemini değiştirerek, [redis] önbelleğinde bulunan jSON dizesinden bir [TaxAdminData] nesnesi oluşturuyoruz (bkz. makale) oluşturmak için [redis] önbelleğinde bulunan jSON dizesini kullanıyoruz:
<?php
namespace Application;
class TaxAdminData extends BaseEntity {
// vergi dilimleri
protected $limites;
protected $coeffR;
protected $coeffN;
// vergi hesaplama sabitleri
protected $plafondQfDemiPart;
protected $plafondRevenusCelibatairePourReduction;
protected $plafondRevenusCouplePourReduction;
protected $valeurReducDemiPart;
protected $plafondDecoteCelibataire;
protected $plafondDecoteCouple;
protected $plafondImpotCouplePourDecote;
protected $plafondImpotCelibatairePourDecote;
protected $abattementDixPourcentMax;
protected $abattementDixPourcentMin;
// başlatma
public function setFromJsonFile(string $taxAdminDataFilename) {
// üst öğe
parent::setFromJsonFile($taxAdminDataFilename);
// öznitelik değerleri kontrol edilir
$this->checkAttributes();
// nesne döndürülür
return $this;
}
protected function check($value): \stdClass {
// $value, string türünde elemanlardan oluşan bir dizi veya tek bir elemandır
if (!\is_array($value)) {
$tableau = [$value];
} else {
$tableau = $value;
}
// string dizisini gerçek sayı dizisine dönüştürülür
$newTableau = [];
$result = new \stdClass();
// dizinin elemanları pozitif veya sıfır olan ondalık sayılar olmalıdır
$modèle = '/^\s*([+]?)\s*(\d+\.\d*|\.\d+|\d+)\s*$/';
for ($i = 0; $i < count($tableau); $i ++) {
if (preg_match($modèle, $tableau[$i])) {
// float değerini newTableau'e yerleştirilir
$newTableau[] = (float) $tableau[$i];
} else {
// hatayı not ediyoruz
$result->erreur = TRUE;
// çıkılır
return $result;
}
}
// sonucu döndürürüz
$result->erreur = FALSE;
if (!\is_array($value)) {
// tek bir değer
$result->value = $newTableau[0];
} else {
// bir değer listesi
$result->value = $newTableau;
}
return $result;
}
// özellik tablosuyla başlatma
public function setFromArrayOfAttributes(array $arrayOfAttributes) {
// üst öğe
parent::setFromArrayOfAttributes($arrayOfAttributes);
// öznitelik değerleri kontrol edilir
$this->checkAttributes();
// nesne döndürülür
return $this;
}
// öznitelik değerlerinin doğrulanması
protected function checkAttributes() {
// öznitelik değerlerinin >=0 olan gerçek sayılar olduğunu kontrol ediyoruz
foreach ($this as $key => $value) {
if (is_string($value)) {
// $value, >=0 olan bir gerçek sayı veya >=0 olan gerçek sayılardan oluşan bir dizi olmalıdır
$result = $this->check($value);
// hata mı?
if ($result->erreur) {
// bir istisna atılır
throw new ExceptionImpots("La valeur de l'attribut [$key] est invalide");
} else {
// değer kaydediliyor
$this->$key = $result->value;
}
}
}
// nesne döndürülüyor
return $this;
}
// getter ve setter'lar
...
}
Açıklamalar
- 5. satır: [TaxAdminData] sınıfı, halihazırda [setFromArrayOfAttributes] yöntemine sahip olan [BaseEntity] sınıfını genişletir. Bu yöntem uygun olmadığından, 67-75. satırlarda yeniden tanımlıyoruz;
- 70. satır: Sınıfın özniteliklerini başlatmak için önce üst sınıfın [setFromArrayOfAttributes] yöntemi kullanılır;
- 72. satır: [checkAttributes] yöntemi, ilişkili değerlerin gerçekten sayı olup olmadığını kontrol eder. Eğer dizgi ise, bunlar sayıya dönüştürülür;
- 74. satır: Oluşturulan [$this] nesnesi, sayısal değerlere sahip özniteliklere sahip bir nesne olur;
- 78-93. satırlar: [checkAttributes] yöntemi, nesnenin özniteliklerine atanan değerlerin gerçekten sayısal olup olmadığını kontrol eder;
- satır 80: öznitelik listesi taranır;
- satır 81: Bir özniteliğin değeri [string] türündeyse;
- satır 83: o zaman bu dizenin bir sayı olup olmadığı kontrol edilir;
- 90. satır: Eğer öyleyse, dize sayıya dönüştürülür ve test edilen özniteliğe atanır;
- satır 85-86: eğer değilse, bir istisna atılır;
- satır 32-65: [check] işlevi, gerekenden biraz daha fazlasını yapar. Hem dizileri hem de tekil değerleri işler. Oysa burada, sadece [string] türündeki bir değeri doğrulamak için çağrılmaktadır. [erreur, value] özelliklerine sahip bir nesne döndürür; burada:
- [erreur], hata olup olmadığını belirten bir boole değeridir;
- [value], 32. satırdaki [value] parametresidir ve duruma göre sayıya veya sayı dizisine dönüştürülür;
[BaseEntity] sınıfı, [arrayOfAttributes] adlı bir özniteliğe sahip olabilirdi; ancak bu özniteliğin artık bulunmaması için değiştirilmiştir: zira bu öznitelik, jSON dizesini [TaxAdminData] ile kirletmektedir. Sınıf şu şekilde yeniden yazılır:
<?php
namespace Application;
class BaseEntity {
// bir dosyadan başlatma JSON
public function setFromJsonFile(string $jsonFilename) {
// vergi verileri dosyasının içeriği alınır
$fileContents = \file_get_contents($jsonFilename);
$erreur = FALSE;
// hata mı?
if (!$fileContents) {
// hatkayı kaydediyoruz
$erreur = TRUE;
$message = "Le fichier des données [$jsonFilename] n'existe pas";
}
if (!$erreur) {
// yapılandırma dosyasından JSON kodunu bir ilişkisel diziye alınır
$arrayOfAttributes = \json_decode($fileContents, true);
// hata mı?
if ($arrayOfAttributes === FALSE) {
// hata kaydediliyor
$erreur = TRUE;
$message = "Le fichier de données JSON [$jsonFilename] n'a pu être exploité correctement";
}
}
// hata mı?
if ($erreur) {
// bir istisna atılır
throw new ExceptionImpots($message);
}
// sınıf özniteliklerinin başlatılması
foreach ($arrayOfAttributes as $key => $value) {
$this->$key = $value;
}
// tüm özniteliklerin varlığı kontrol ediliyor
$this->checkForAllAttributes($arrayOfAttributes);
// nesne döndürülüyor
return $this;
}
public function checkForAllAttributes($arrayOfAttributes) {
// tüm anahtarların başlatıldığını kontrol ediliyor
foreach (\array_keys($arrayOfAttributes) as $key) {
if (!isset($this->$key)) {
throw new ExceptionImpots("L'attribut [$key] de la classe "
. get_class($this) . " n'a pas été initialisé");
}
}
}
public function setFromArrayOfAttributes(array $arrayOfAttributes) {
// sınıfın belirli öznitelikleri (hepsi değil) başlatılır
foreach ($arrayOfAttributes as $key => $value) {
$this->$key = $value;
}
// nesne döndürülür
return $this;
}
// toString
public function __toString() {
// nesnenin öznitelikleri
$arrayOfAttributes = \get_object_vars($this);
// nesnenin jSON dizesi
return \json_encode($arrayOfAttributes, JSON_UNESCAPED_UNICODE);
}
}
Açıklamalar
- 20. satır: [$this→arrayOfAttributes] özniteliği, artık [checkForAllAttributes] yöntemine aktarılması gereken bir değişkene dönüştürülmüştür; bu yöntem, daha önce [$this→arrayOfAttributes] özniteliği üzerinde işlem yapıyordu (38. satır);
[BaseEntity] üzerindeki bu değişiklik nedeniyle, [Database] sınıfında da küçük bir değişiklik yapılması gerekmektedir:
<?php
namespace Application;
class Database extends BaseEntity {
// özellikler
protected $dsn;
protected $id;
protected $pwd;
protected $tableTranches;
protected $colLimites;
protected $colCoeffR;
protected $colCoeffN;
protected $tableConstantes;
protected $colPlafondQfDemiPart;
protected $colPlafondRevenusCelibatairePourReduction;
protected $colPlafondRevenusCouplePourReduction;
protected $colValeurReducDemiPart;
protected $colPlafondDecoteCelibataire;
protected $colPlafondDecoteCouple;
protected $colPlafondImpotCelibatairePourDecote;
protected $colPlafondImpotCouplePourDecote;
protected $colAbattementDixPourcentMax;
protected $colAbattementDixPourcentMin;
// ayarlayıcı
// başlatma
public function setFromJsonFile(string $jsonFilename) {
// üst nesne
parent::setFromJsonFile($jsonFilename);
// nesneyi döndürür
return $this;
}
// alıcı ve ayarlayıcılar
...
}
Açıklamalar
- Orijinal kodda, 30. satırdan sonra [parent::checkForAllAttributes] yöntemi çağrılıyordu. Artık bu işlem yapılmasına gerek yoktur, çünkü bu işlem [parent::setFromJsonFile($jsonFilename)] yöntemi tarafından otomatik olarak gerçekleştirilmektedir;
14.3.4. Sunucudaki [Postman] testleri
[Postman], bağlantıdaki makalede tanıtılmıştır.
Aşağıdaki Postman testlerini kullanıyoruz:



Bu son isteğin sonucu olan jSON şu şekildedir:

- [5-8]'te, jSON dizesinin özniteliklerinin sayısal değerlere (karakter dizileri değil) sahip olduğu görülebilir. Bu sonuç, [Métier] JavaScript sınıfının normal şekilde çalışmasını sağlayacaktır;
14.3.5. Ana komut dosyası [main]

Javascript istemcisinin ana komut dosyası [main] şu şekildedir:
// ithalatlar
import axios from 'axios';
// ithalatlar
import Dao from './Dao2';
import Métier from './Métier';
// asenkron işlev [main]
async function main() {
// axios yapılandırması
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';
// katman örneği oluşturma [dao]
const dao = new Dao(axios);
// istekler HTTP
let taxAdminData;
try {
// oturum başlatma
log("-----------init-session");
let response = await dao.initSession();
log(response);
// kimlik doğrulama
log("-----------authentifier-utilisateur");
response = await dao.authentifierUtilisateur("admin", "admin");
log(response);
// vergi verileri
log("-----------get-admindata");
response = await dao.getAdminData();
log(response);
taxAdminData = response.réponse;
} catch (error) {
// hata günlüğüne kaydediliyor
console.log("erreur=", error.message);
// son
return;
}
// [métier] katmanının örneklenmesi
const métier = new Métier(taxAdminData);
// vergi hesaplamaları
log("-----------calculer-impot x 3");
const simulations = [];
simulations.push(métier.calculerImpot("oui", 2, 45000));
simulations.push(métier.calculerImpot("non", 2, 45000));
simulations.push(métier.calculerImpot("non", 1, 30000));
// simülasyon listesi
log("-----------liste-des-simulations");
log(simulations);
// simülasyon silme
log("-----------suppression simulation n° 1");
simulations.splice(1, 1);
log(simulations);
}
// günlük jSON
function log(object) {
console.log(JSON.stringify(object, null, 2));
}
// çalıştırma
main();
Açıklamalar
- 5-6. satırlar: [Dao] ve [Métier] sınıflarının içe aktarılması;
- 9. satır: [Dao] sınıfı aracılığıyla sunucuyla iletişimi düzenleyecek ve [Métier] sınıfından vergi hesaplamalarını yapmasını isteyecek olan asenkron [main] işlevi;
- 10-36. satırlar: komut dosyası, [dao] katmanındaki [initSession, authentifierUtilisateur, getAdminData] yöntemlerini sırayla ve engelleyici bir şekilde çağırır;
- satır 38: Artık [dao] katmanına ihtiyaç yoktur. JavaScript istemcisinin [métier] katmanını çalıştırmak için gerekli tüm öğeler mevcuttur;
- 41-46. satırlar: Üç vergi hesaplaması yapıyoruz ve sonuçları [simulations] tablosunda topluyoruz;
- 49. satır: Simülasyon tablosunu görüntülüyoruz;
- 52. satır: Bunlardan birini siliyoruz;
Ana komut dosyasının çalıştırılması sonucunda elde edilen sonuçlar şunlardır:
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\client impôts\client http 2\main2.js"
"-----------init-session"
{
"action": "init-session",
"état": 700,
"réponse": "session démarrée avec type [json]"
}
"-----------authentifier-utilisateur"
{
"action": "authentifier-utilisateur",
"état": 200,
"réponse": "Authentification réussie [admin, admin]"
}
"-----------get-admindata"
{
"action": "get-admindata",
"état": 1000,
"réponse": {
"limites": [
9964,
27519,
73779,
156244,
0
],
"coeffR": [
0,
0.14,
0.3,
0.41,
0.45
],
"coeffN": [
0,
1394.96,
5798,
13913.69,
20163.45
],
"plafondQfDemiPart": 1551,
"plafondRevenusCelibatairePourReduction": 21037,
"plafondRevenusCouplePourReduction": 42074,
"valeurReducDemiPart": 3797,
"plafondDecoteCelibataire": 1196,
"plafondDecoteCouple": 1970,
"plafondImpotCouplePourDecote": 2627,
"plafondImpotCelibatairePourDecote": 1595,
"abattementDixPourcentMax": 12502,
"abattementDixPourcentMin": 437
}
}
"-----------calculer-impot x 3"
"-----------liste-des-simulations"
[
{
"impôt": 502,
"surcôte": 0,
"décôte": 857,
"réduction": 126,
"taux": 0.14
},
{
"impôt": 3250,
"surcôte": 370,
"décôte": 0,
"réduction": 0,
"taux": 0.3
},
{
"impôt": 1687,
"surcôte": 0,
"décôte": 0,
"réduction": 0,
"taux": 0.14
}
]
"-----------suppression simulation n° 1"
[
{
"impôt": 502,
"surcôte": 0,
"décôte": 857,
"réduction": 126,
"taux": 0.14
},
{
"impôt": 1687,
"surcôte": 0,
"décôte": 0,
"réduction": 0,
"taux": 0.14
}
]
[Done] exited with code=0 in 0.583 seconds
14.4. Müşteri HTTP 3

Bu bölümde, [Client HTTP 2] uygulamasını aşağıdaki mimariye göre bir tarayıcıya taşıyoruz:

Taşıma işlemi anında gerçekleşmez. [node.js], ES6 JavaScript'i çalıştırabilse de, tarayıcılar genellikle bunu yapamaz. Bu durumda, ES6 kodunu, yeni tarayıcılar tarafından anlaşılabilen ES5 koduna çeviren araçlar kullanmak gerekir. Neyse ki bu araçlar hem güçlü hem de kullanımı oldukça basittir.
Burada [How to write ES6 code that’s safe to run in the browser - Web Developer's Journal] başlıklı makaleyi takip ettik.
[client HTTP 3/src] klasörüne, az önce geliştirdiğimiz [Client Http 2] uygulamasının [main.js, Métier.js, Dao2.js] öğelerini yerleştirdik.
14.4.1. Projenin başlatılması
[client http 3] klasöründe çalışacağız. [VSCode] içinde bir terminal açıyoruz ve bu klasöre geçiyoruz:

[npm init] komutuyla bu projeyi başlatıyoruz ve sorulan sorulara varsayılan yanıtları kabul ediyoruz:

- [4-5]'te, verilen çeşitli yanıtlardan oluşturulan [package.json] proje yapılandırma dosyası;
14.4.2. Proje bağımlılıklarının kurulumu
Aşağıdaki bağımlılıkları kuracağız:
- [@babel/core]: [Babel] ve [https://babeljs.io] araçlarının çekirdeği olup, 2015 ve sonrası sürümlerdeki ES kodunu hem yeni hem de eski tarayıcılarda çalıştırılabilir koda dönüştürür;
- [@babel/preset-env]: Babel araç setinin bir parçasıdır. ES6 → ES5 dönüştürme işleminden önce devreye girer;
- [babel-loader]: Bu bağımlılık, [webpack] aracının [Babel] aracını çağırmasına olanak tanır;
- [webpack]: orkestra şefi. [webpack], ES6 → ES5 kodlarının derlenmesini gerçekleştirmek için Babel'i çağırır ve ardından ortaya çıkan tüm dosyaları tek bir dosyada birleştirir;
- [webpack-cli]: [webpack] için gereklidir;
- [@webpack-cli/init]: [webpack]'i yapılandırmak için kullanılır;
- [webpack-dev-server]: Varsayılan olarak 8080 numaralı bağlantı noktasında çalışan bir geliştirme web sunucusu sağlar. Kaynak dosyalar değiştirildiğinde, web uygulamasını otomatik olarak yeniden yükler;
Projenin bağımlılıkları, [VSCode] terminalinde şu şekilde kurulur:
npm --save-dev install @babel/core @babel/preset-env babel-loader webpack webpack-cli webpack-dev-server @webpack-cli/init

Bağımlılıklar yüklendikten sonra, [package.json] dosyası şu şekilde değişmiştir:
{
"name": "client-http-3",
"version": "1.0.0",
"description": "client jS du serveur de calcul de l'impôt",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "serge.tahe@gmail.com",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.6.0",
"@babel/preset-env": "^7.6.0",
"@webpack-cli/init": "^0.2.2",
"babel-loader": "^8.0.6",
"cross-env": "^6.0.0",
"webpack": "^4.40.2",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.1"
}
}
- 12-19. satırlar: Projenin bağımlılıkları [devDependencies]'tir: Bunlara geliştirme aşamasında ihtiyaç vardır, ancak üretim aşamasında artık gerekmez. Nitekim, üretimde [dist/main.js] dosyası kullanılır. Bu dosya ES5 olarak kodlanmıştır ve artık ES6 kodunu ES5 koduna dönüştüren araçlara ihtiyaç duymamaktadır;
Projeye iki bağımlılık eklememiz gerekiyor:
- [core-js]: ECMAScript 2019 için “polyfill’ler” içerir. Bir polyfill, ECMAScript 2019 (Eylül 2019) gibi yeni bir kodu eski tarayıcılarda çalıştırmayı sağlar;
- [regenerator-runtime]: kütüphane sitesine göre --> [Source transformer enabling ECMAScript 6 generator functions in JavaScript-of-today];
Bu iki bağımlılık, Babel 7'den itibaren, daha önce bu rolü üstlenen ve şu anda (Eylül 2019) kullanımdan kaldırılan [@babel/polyfill] bağımlılığının yerini almaktadır. Bunlar şu şekilde kurulur:

[package.json] dosyası ise şu şekilde değişir:
{
"name": "client-http-3",
"version": "1.0.0",
"description": "My webpack project",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack-dev-server"
},
"author": "serge.tahe@gmail.com",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.6.0",
"@babel/preset-env": "^7.6.0",
"@webpack-cli/init": "^0.2.2",
"babel-loader": "^8.0.6",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.40.2",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.1"
},
"dependencies": {
"core-js": "^3.2.1",
"regenerator-runtime": "^0.13.3"
}
}
[core-js, regenerator-runtime] bağımlılıklarının kullanılması, aşağıdaki [imports] kodlarını (3-4. satırlar) ana komut dosyası olan [src/main.js]'e eklemeyi gerektirir:
// içe aktarmalar
import axios from 'axios';
import "core-js/stable";
import "regenerator-runtime/runtime";
// ithalatlar
import Dao from './Dao2';
import Métier from './Métier';
14.4.3. [webpack]'in yapılandırılması
[webpack], aşağıdakileri yönetecek araçtır:
- projedeki tüm JavaScript dosyalarının ES6 → ES5 dönüştürülmesi;
- oluşturulan dosyaların tek bir dosyada birleştirilmesini;
Bu araç, [webpack.config.js] adlı bir yapılandırma dosyası tarafından yönetilir ve bu dosya, [@webpack-cli/init] adlı bir bağımlılık sayesinde oluşturulabilir (Eylül 2019). Bu bağımlılık, bağlantı paragrafında belirtilen diğerleriyle birlikte kurulmuştur.
[VSCode] terminalinde [npx webpack-cli init] komutunu çalıştırıyoruz:

Çeşitli soruları yanıtladıktan sonra (varsayılan olarak önerilen yanıtların çoğunu kabul edebiliriz), [4] projesinin kök dizininde [webpack.config.js] dosyası oluşturulur:
[webpack.config.js] dosyası şu şekilde görünür:
/* eslint-disable */
const path = require('path');
const webpack = require('webpack');
/*
* SplitChunksPlugin is enabled by default and replaced
* deprecated CommonsChunkPlugin. It automatically identifies modules which
* should be splitted of chunk by heuristics using module duplication count and
* module category (i. e. node_modules). And splits the chunks…
*
* It is safe to remove "splitChunks" from the generated configuration
* and was added as an educational example.
*
* https://webpack.js.org/plugins/split-chunks-plugin/
*
*/
const HtmlWebpackPlugin = require('html-webpack-plugin');
/*
* We've enabled HtmlWebpackPlugin for you! This generates a html
* page for you when you compile webpack, which will make you start
* developing and prototyping faster.
*
* https://github.com/jantimon/html-webpack-plugin
*
*/
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: '[name].[chunkhash].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [new webpack.ProgressPlugin(), new HtmlWebpackPlugin()],
module: {
rules: [
{
test: /.(js|jsx)$/,
include: [path.resolve(__dirname, 'src')],
loader: 'babel-loader',
options: {
plugins: ['syntax-dynamic-import'],
presets: [
[
'@babel/preset-env',
{
modules: false
}
]
]
}
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
vendors: {
priority: -10,
test: /[\\/]node_modules[\\/]/
}
},
chunks: 'async',
minChunks: 1,
minSize: 30000,
name: true
}
},
devServer: {
open: true
}
};
Bu dosyanın tüm ayrıntılarını tam olarak anlamıyorum, ancak birkaç noktaya dikkat çekilebilir:
- 1. satır: Dosya, ES6 kodunu içermiyor. Bu durumda [Eslint], [javascript] projesinin kök dizinine kadar uzanan hatalar bildiriyor. Bu can sıkıcı bir durum. Eslint’in bir dosyayı analiz etmesini önlemek için 1. satırı yorum satırına dönüştürmek yeterlidir;
- 31. satır: [développement] modunda çalışıyoruz;
- 32. satır: giriş komut dosyası burada [src/index.js]. Bunu değiştirmemiz gerekecek;
- 36. satır: [webpack]'in çıktılarının kaydedileceği klasör, [dist] klasörü olacaktır;
- satır 46: [webpack]'in, kurduğumuz bağımlılıklardan biri olan [babel-loader]'i kullandığını görüyoruz;
- 54. satır: [webpack]'in, kurduğumuz bağımlılıklardan biri olan [@babel-preset/env]'i kullandığı görülüyor;
[webpack]'in başlatılması, [package.json] dosyasını değiştirdi (izin istiyor):
{
"name": "client-http-3",
"version": "1.0.0",
"description": "My webpack project",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack-dev-server"
},
"author": "serge.tahe@gmail.com",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.6.0",
"@babel/preset-env": "^7.6.0",
"@webpack-cli/init": "^0.2.2",
"babel-loader": "^8.0.6",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.40.2",
"webpack-cli": "^3.3.9",
"webpack-dev-server": "^3.8.1"
},
"dependencies": {
"core-js": "^3.2.1",
"regenerator-runtime": "^0.13.3"
}
}
- 4. satır: değiştirilmiştir;
- 8-9, 18-19. satırlar: bunlar eklenmiştir;
- 8. satır: projeyi derlemeye olanak tanıyan [npm] görevi;
- 9. satır: projeyi çalıştırmaya yarayan [npm] görevi;
- satır 18: ?
- 19. satır: [webpack] tarafından oluşturulan [dist/main.js] komut dosyasını otomatik olarak içeren bir [dist/index.html] dosyasının oluşturulmasını sağlar ve proje çalıştırıldığında bu dosya kullanılır;
Son olarak, [webpack] yapılandırması bir [src/index.js] dosyası oluşturdu:

[index.js] dosyasının içeriği şöyledir (Eylül 2019):
console.log("Hello World from your main file!");
14.4.4. Projenin derlenmesi ve çalıştırılması
[package.json] dosyası, üç [npm] görevine sahiptir:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"start": "webpack-dev-server"
},
Bu görevler, [VSCode] tarafından kapsanır ve yürütülmek üzere sunulur:

- [1-3]'te proje derlenir;
- [4]'te: proje, [dist/main.hash.js]'te derlenir ve [dist/index.html] sayfası oluşturulur;
Oluşturulan [index.html] sayfası şöyledir:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
</head>
<body>
<script type="text/javascript" src="main.87afc226fd6d648e7dea.js"></script></body>
</html>
Dolayısıyla bu sayfa, [webpack] tarafından oluşturulan [main.hash.js] dosyasını kapsüllemekten ibarettir.
Proje, [start] görevi tarafından yürütülür:

[dist/index.html] sayfası daha sonra, yerel makinenin 8080 numaralı bağlantı noktasında çalışan ve [webpack] paketine ait bir sunucuya yüklenir ve makinenin varsayılan tarayıcısı tarafından görüntülenir:

- [2]'te, [webpack] web sunucusunun hizmet bağlantı noktası;
- [3]'e, [dist/index.html] sayfasının gövdesi boştur;
- [4]'te, tarayıcının geliştirici araçları sekmesi (burada Firefox: [console]) (F12);
- [5]'te, [src/index.js] dosyasının çalıştırılmasının sonucu. Bu dosyanın içeriğinin şu şekilde olduğunu hatırlatırız:
Şimdi bu içeriği şu satırla değiştirelim:
Otomatik olarak (yeniden derlemeye gerek kalmadan), yeni [main.js, index.html] dosyaları oluşturulur ve yeni [index.html] dosyası tarayıcıya yüklenir:

[build] görevini [start] görevinden önce çalıştırmaya gerek yoktur: [start] görevi, önce projenin derlemesini gerçekleştirir. Bu derleme sonuçlarını [dist] klasörüne kaydetmez. Bunu fark etmek için bu klasörü silmek yeterlidir. Böylece, [start] görevinin, [dist] klasörünü oluşturmadan projeyi derleyip çalıştırdığı görülecektir. Görünüşe göre [index.html, main.hash.js] derleme çıktılarını, [webpackdev-server]'e özgü bir klasörde saklıyor. Bu davranış, testlerimiz için yeterlidir.
Geliştirme sunucusu başlatıldığında, proje dosyalarından herhangi birinde kaydedilen her değişiklik yeniden derlemeye neden olur. Bu nedenle, [VSCode]’in [Auto Save] modunu devre dışı bırakıyoruz. Zira, proje dosyalarından herhangi birine karakter girildiğinde yeniden derleme yapılmasını istemiyoruz. Yeniden derleme işleminin yalnızca değişiklikler kaydedildiğinde gerçekleşmesini istiyoruz:

- [2]'te, [Auto Save] seçeneği işaretlenmemelidir;
14.4.5. Vergi hesaplama sunucusunun JavaScript istemcisinin test edilmesi
Vergi hesaplama sunucusunun JavaScript istemcisini test etmek için, [main.js] [1] dosyasını, [webpack.config.js] [2-3] dosyasında projenin giriş noktası olarak belirlemelisiniz:

[main.js] komut dosyasının, [Client http 2]'teki sürümüne kıyasla iki ek import içermesi gerektiğini unutmayalım:

Ayrıca, sunucunun gönderebileceği hataları yönetmek için kodu biraz değiştirdik:
// imports
import axios from 'axios';
import "core-js/stable";
import "regenerator-runtime/runtime";
// ithalatlar
import Dao from './Dao2';
import Métier from './Métier';
// asenkron işlev [main]
async function main() {
// axios yapılandırması
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';
// katman örneği oluşturma [dao]
const dao = new Dao(axios);
// istekler HTTP
let taxAdminData;
try {
// oturum başlatma
log("-----------init-session");
let response = await dao.initSession();
log(response);
if (response.état != 700) {
throw new Error(JSON.stringify(response.réponse));
}
// kimlik doğrulama
log("-----------authentifier-utilisateur");
response = await dao.authentifierUtilisateur("admin", "admin");
log(response);
if (response.état != 200) {
throw new Error(JSON.stringify(response.réponse));
}
// vergi verileri
log("-----------get-admindata");
response = await dao.getAdminData();
log(response);
if (response.état != 1000) {
throw new Error(JSON.stringify(response.réponse));
}
taxAdminData = response.réponse;
} catch (error) {
// hata günlüğüne kaydediliyor
console.log("erreur=", error.message);
// son
return;
}
// katman örneği oluşturma [métier]
const métier = new Métier(taxAdminData);
// vergi hesaplamaları
log("-----------calculer-impot x 3");
const simulations = [];
simulations.push(métier.calculerImpot("oui", 2, 45000));
simulations.push(métier.calculerImpot("non", 2, 45000));
simulations.push(métier.calculerImpot("non", 1, 30000));
// simülasyon listesi
log("-----------liste-des-simulations");
log(simulations);
// simülasyon silme
log("-----------suppression simulation n° 1");
simulations.splice(1, 1);
log(simulations);
}
// günlük kaydı jSON
function log(object) {
console.log(JSON.stringify(object, null, 2));
}
// çalıştırma
main();
Yorumlar
- [24-26], [31-33] ve [38-40] satırlarında, sunucunun jSON yanıtında gönderilen [response.état] kodu test edilmektedir. Bu kod bir hatayı işaret ediyorsa, [response.réponse] sunucu yanıtındaki jSON hata mesajını içeren bir istisna atılır;
Bu işlem tamamlandıktan sonra, [5-6] projesini çalıştırırız.
Bunun üzerine [index.html] sayfası oluşturulur ve tarayıcıya yüklenir:

- [7]'te, [init-session] eyleminin bir [CORS] sorunu (Cross-Origin Resource Sharing) nedeniyle tamamlanamadığı görülüyor;
CORS sorunu, istemci/sunucu ilişkisinde kaynaklanmaktadır:
- Javascript istemcimiz [http://localhost:8080] makinesine indirilmiştir;
- vergi hesaplama sunucusu [http://localhost:80] makinesinde çalışıyor;
- bu durumda istemci ve sunucu aynı etki alanlarında değildir (aynı makine üzerinde ancak aynı bağlantı noktasında değil);
- [http://localhost:8080] makinesinden yüklenen JavaScript istemcisini çalıştıran tarayıcı, [http://localhost:80]'i hedef almayan tüm istekleri engelliyor. Bu bir güvenlik önlemidir. Dolayısıyla, [http://localhost:80] makinesinde çalışan sunucuya yönelik istemci isteğini de engeller;
Aslında tarayıcı, isteği tamamen engellemez. Tarayıcı, sunucunun etki alanları arası istekleri kabul ettiğini “bildirmesini” bekler. Bu izni alırsa, tarayıcı etki alanları arası isteği iletir.
Sunucu, belirli HTTP başlıklarını göndererek izin verir:
- 1. satır: JavaScript istemcisi, [http://localhost:8080] etki alanında işlem yapmaktadır. Sunucu, bu etki alanını kabul ettiğini açıkça belirtmelidir;
- 2. satır: JavaScript istemcisi, isteklerinde HTTP ve [Accept, Content-Type] başlıklarını kullanacaktır:
- [Accept]: Bu başlık tüm isteklerde gönderilir;
- [Content-Type]: Bu başlık, POST işlemlerinde POST parametrelerinin türünü belirtmek için kullanılır;
Sunucu, bu iki HTTP başlığını açıkça kabul etmelidir;
- 3. satır: Javascript istemcisi, GET ve POST isteklerini kullanacaktır. Sunucu, bu iki istek türünü açıkça kabul etmelidir;
- satır 4: JavaScript istemcisi oturum çerezleri gönderecektir. Sunucu, bunları satır 4'teki başlıkla kabul eder;
Bu nedenle sunucuda değişiklik yapmamız gerekiyor. Bunu [Netbeans] dosyasında gerçekleştiriyoruz. CORS ile ilgili sorun, yalnızca geliştirme modunda karşılaşılan bir sorundur. Üretim ortamında, istemci ve sunucu aynı [http://localhost:80] etki alanında çalışacak ve CORS sorunu yaşanmayacaktır. Dolayısıyla, sunucu yapılandırması yoluyla CORS isteklerini izin verme veya reddetme yöntemine ihtiyacımız var.

Sunucu değişiklikleri üç farklı yerde yapılır:
- [1, 4]: [config.json] yapılandırma dosyasında, etki alanları arası isteklerin kabul edilip edilmeyeceğini kontrol edecek bir boole değeri eklemek için;
- [2]: JavaScript istemcisine yanıtı gönderen [ParentResponse] sınıfında. İstemci tarayıcısının beklediği CORS başlıklarını gönderen bu sınıftır;
- [3]: Sırasıyla [html, json, xml] oturumları için yanıtları oluşturan [HtmlResponse, JsonResponse, XmlResponse] sınıflarında yer alır. Bu sınıflar, [4] içinde bulunan [corsAllowed] boole değerini üst sınıfları olan [2]'e aktarmalıdır. Bu işlem, [5] sınıfında, jSON ve [2] dosyalarındaki görüntü dizisini aktararak gerçekleştirilir;
[ParentResponse] ve [2] sınıfları şu şekilde gelişir:
<?php
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
class ParentResponse {
// int $statusCode : yanıt durum kodu HTTP
// $content dizesi: gönderilecek yanıt gövdesi
// duruma göre, JSON, XML veya HTML dizesidir
// dizi $headers: yanıtta eklenecek başlıklar HTTP
public function sendResponse(
Request $request,
int $statusCode,
string $content,
array $headers,
array $config): void {
// sunucudan gelecek metin yanıtının hazırlanması
$response = new Response();
$response->setCharset("utf-8");
// durum kodu
$response->setStatusCode($statusCode);
// etki alanları arası istekler için başlıklar
if ($config['corsAllowed']) {
$origin = $request->headers->get("origin");
if (strpos($origin, "http://localhost") === 0) {
$headers = array_merge($headers,
["Access-Control-Allow-Origin" => $origin,
"Access-Control-Allow-Headers" => "Accept, Content-Type",
"Access-Control-Allow-Methods" => "GET, POST",
"Access-Control-Allow-Credentials" => "true"
]);
}
}
foreach ($headers as $text => $value) {
$response->headers->set($text, $value);
}
// [OPTIONS] yönteminin özel durumu
// bu durumda yalnızca başlıklar önemlidir
$method = strtolower($request->getMethod());
if ($method === "options") {
$content = "";
$response->setStatusCode(Response::HTTP_OK);
}
// yanıt gönderilir
$response->setContent($content);
$response->send();
}
}
- 29. satır: Etki alanları arası isteklerin yönetilmesi gerekip gerekmediğine bakılır. Gerekiyorsa, mevcut istek bir etki alanları arası istek olmasa bile HTTP ve CORS başlıkları (33-37. satırlar) oluşturulur. Bu durumda, CORS başlıkları gereksiz hale gelir ve istemci tarafından kullanılmaz;
- 30. satır: Etki alanları arası bir istekte, sunucuya sorgu gönderen istemci tarayıcı, HTTP ve [Origin: http://localhost:8080] başlıklarını gönderir (özellikle bizim JavaScript istemcimizin durumunda). 30. satırda, HTTP başlığı, [$request] isteğinden alınır;
- 31. satır: Yalnızca [http://localhost] makinesinden gelen etki alanı arası istekler kabul edilecektir. Bu isteklerin yalnızca projenin geliştirme modunda gerçekleştiğini hatırlatırız;
- satır 32-36: CORS başlıkları, [$headers] tablosunda halihazırda bulunan başlıklara eklenir;
- 45-49. satırlar: İstemci tarayıcısının CORS izinlerini talep etme şekli, çalıştırılan istemciye göre farklılık gösterebilir. Bazen istemci tarayıcısı bu izinleri HTTP [OPTIONS] komutuyla talep edebilir. Bu, yalnızca [GET, POST] komutlarını işlemek üzere tasarlanmış sunucumuz için yeni bir durumdur. [OPTIONS] komutu durumunda, sunucu şu anda bir hata yanıtı üretmektedir. 46-49. satırlarda, bunu son anda düzeltiyoruz: 46. satırda, geçerli komutun bir [OPTIONS] komutu olduğunu tespit edersek, o zaman istemciye şunları üretiriz:
- 47. ve 51. satırlar: boş bir [$content] yanıtı;
- 48. satır: komutun başarılı olduğunu belirten 200 durum kodu. Bu komut için önemli olan tek şey, 33-36. satırlardaki CORS başlıklarının gönderilmesidir. İstemci tarayıcısı bunu beklemektedir;
Sunucu bu şekilde düzeltildikten sonra, JavaScript istemcisi daha iyi çalışıyor ancak yeni bir hata ortaya çıkıyor:

- [1]'te, jSON oturumu doğru şekilde başlatılır;
- [2]'te, [authentifier-utilisateur] eylemi başarısız oluyor: sunucu, aktif bir oturum olmadığını bildiriyor. Bu, JavaScript istemcisinin [init-session] eylemi sırasında gönderdiği oturum çerezini sunucuya doğru şekilde geri gönderemediği anlamına gelir;
Gerçekleşen ağ iletişimini inceleyelim:

- [4]'te, [init-session] isteği. Bu istek, yanıt durumu kodu 200 ile başarıyla sonuçlandı;
- [5]'e, [authentifier-utilisateur] isteği. Bu istek, yanıt durumu olarak 400 (Bad Request) [6] kodu ile başarısız oldu;
[5] isteğinin HTTP ve [7] başlıklarını incelersek, Javascript istemcisinin, sunucu tarafından başlangıçta gönderilen oturum çerezini geri göndermesini sağlayacak olan HTTP ve [Cookie] başlıklarını göndermediği görülür. Bu nedenle sunucu, oturumun mevcut olmadığını bildirir.
İstemcinin oturum çerezini gönderebilmesi için, [axios] nesnesine bir yapılandırma eklenmesi gerekir:
// ithalatlar
import axios from 'axios';
import "core-js/stable";
import "regenerator-runtime/runtime";
// ithalatlar
import Dao from './Dao2';
import Métier from './Métier';
// asenkron işlev [main]
async function main() {
// axios yapılandırması
axios.defaults.timeout = 2000;
axios.defaults.baseURL = 'http://localhost/php7/scripts-web/impots/version-14';
axios.defaults.withCredentials = true;
// katman örneği oluşturma [dao]
const dao = new Dao(axios);
// istekler HTTP
let taxAdminData;
...
- satır, çerezlerin [axios] isteğinin HTTP başlıklarına eklenmesini talep eder. Bunun [node.js] ortamında gerekli olmadığını belirtelim. Dolayısıyla, iki ortam arasında kod farklılıkları bulunmaktadır.
Bu hata düzeltildikten sonra, JavaScript istemcisi normal şekilde çalışır:


14.5. HTTP 3 istemcisinin iyileştirilmesi
Önceki [Dao2] sınıfı bir tarayıcıda çalıştırıldığında, oturum çerezinin yönetilmesine gerek kalmaz. Zira, [dao] katmanını barındıran tarayıcı, oturum çerezini yönetir: sunucunun kendisine gönderdiği tüm çerezleri otomatik olarak geri gönderir. Bu nedenle, [Dao2] sınıfı aşağıdaki [Dao3] sınıfı olarak yeniden yazılabilir:
"use strict";
// ithalatlar
import qs from "qs";
class Dao3 {
// oluşturucu
constructor(axios) {
this.axios = axios;
}
// oturum başlatma
async initSession() {
// istek seçenekleri HHTP [get /main.php?action=init-session&type=json]
const options = {
method: "GET",
// URL parametreleri
params: {
action: "init-session",
type: "json"
}
};
// HTTP sorgusunun yürütülmesi
return await this.getRemoteData(options);
}
async authentifierUtilisateur(user, password) {
// HHTP sorgusunun seçenekleri [post /main.php?action=authentifier-utilisateur]
const options = {
method: "POST",
headers: {
"Content-type": "application/x-www-form-urlencoded"
},
// POST'in gövdesi
data: qs.stringify({
user: user,
password: password
}),
// URL parametreleri
params: {
action: "authentifier-utilisateur"
}
};
// HTTP sorgusunun yürütülmesi
return await this.getRemoteData(options);
}
async getAdminData() {
// HHTP sorgusunun seçenekleri [get /main.php?action=get-admindata]
const options = {
method: "GET",
// URL parametreleri
params: {
action: "get-admindata"
}
};
// HTTP sorgusunun yürütülmesi
const data = await this.getRemoteData(options);
// sonuç
return data;
}
async getRemoteData(options) {
// HTTP sorgusunun yürütülmesi
let response;
try {
// asenkron istek
response = await this.axios.request("main.php", options);
} catch (error) {
// [error] parametresi bir istisna örneğidir - çeşitli biçimlerde olabilir
if (error.response) {
// sunucunun yanıtı [error.response]'tedir
response = error.response;
} else {
// hata yeniden gönderilir
throw error;
}
}
// yanıt, sunucudan gelen HTTP yanıtının tamamıdır (HTTP başlıkları + yanıtın kendisi)
// sunucunun yanıtı [response.data] içinde yer alıyor
return response.data;
}
}
// sınıfın dışa aktarımı
export default Dao3;
Yönetim çerezinin yönetimi ile ilgili her şey ortadan kalkmıştır.
Önceki projeyi şu şekilde değiştiriyoruz:

[src] klasörüne iki dosya ekledik:
- az önce tanıttığımız [Dao3] sınıfı;
- yeni sürümü başlatmakla görevli [main3] dosyası;
[main3] dosyası, önceki sürümdeki [main] dosyasıyla aynı kalır, ancak artık [Dao3] sınıfını kullanır:
// ithalatlar
import axios from "axios";
import "core-js/stable";
import "regenerator-runtime/runtime";
// ithalatlar
import Dao from "./Dao3";
import Métier from "./Métier";
// asenkron işlev [main]
async function main() {
// axios yapılandırması
axios.defaults.timeout = 2000;
axios.defaults.baseURL =
"http://localhost/php7/scripts-web/impots/version-14";
axios.defaults.withCredentials = true;
// katman örneği oluşturma [dao]
const dao = new Dao(axios);
// istekler HTTP
...
}
// jSON günlüğü
function log(object) {
console.log(JSON.stringify(object, null, 2));
}
// çalıştırma
main();
[webpack.config] dosyası, artık [main3] komut dosyasını çalıştıracak şekilde değiştirilmiştir:
/* eslint-disable */
const path = require("path");
const webpack = require("webpack");
/*
* SplitChunksPlugin is enabled by default and replaced
* deprecated CommonsChunkPlugin. It automatically identifies modules which
* should be splitted of chunk by heuristics using module duplication count and
* module category (i. e. node_modules). And splits the chunks…
*
* It is safe to remove "splitChunks" from the generated configuration
* and was added as an educational example.
*
* https://webpack.js.org/plugins/split-chunks-plugin/
*
*/
const HtmlWebpackPlugin = require("html-webpack-plugin");
/*
* We've enabled HtmlWebpackPlugin for you! This generates a html
* page for you when you compile webpack, which will make you start
* developing and prototyping faster.
*
* https://github.com/jantimon/html-webpack-plugin
*
*/
module.exports = {
mode: "development",
//giriş: "./src/mainjs",
entry: "./src/main3.js",
output: {
filename: "[name].[chunkhash].js",
path: path.resolve(__dirname, "dist")
},
plugins: [new webpack.ProgressPlugin(), new HtmlWebpackPlugin()],
...
};
Bu işlem tamamlandıktan sonra, vergi hesaplama sunucusunu başlattıktan sonra projeyi çalıştırıyoruz:

Tarayıcı konsolunda elde edilen sonuçlar, önceki sürümdekilerle aynıdır.
14.6. Conclusion
Artık bir web uygulamasının JavaScript kodunu geliştirmek için gerekli tüm araçlara sahibiz. Şunları yapabiliriz:
- en güncel ECMAScript kodunu kullanabiliriz;
- hata ayıklama ve testler için daha basit bir ortamda bu kodun tek tek öğelerini test etmek;
- daha sonra [babel] ve [webpack] araçlarını kullanarak bu kodu bir tarayıcıya aktarmak;