23. Uygulama alıştırması – sürüm 12
Bu bölümde, MVC mimarisine (Model-Görünüm-Denetleyici) uygun bir web uygulaması yazacağız. Uygulama, yanıtlarını üç farklı formatta sunabilecektir: jSON, XML, HTML. Şimdi yapacağımız şey ile daha önce yapılanlar arasında karmaşıklık açısından büyük bir fark vardır. Şimdiye kadar gördüğümüz kavramların çoğunu yeniden kullanacağız ve nihai uygulamaya giden tüm adımları ayrıntılı olarak ele alacağız.
23.1. MVC Mimarisi
MVC olarak adlandırılan mimari modelini (Model – Görünüm – Denetleyici) şu şekilde uygulayacağız:

Bir istemciden gelen isteğin işlenmesi şu şekilde gerçekleşecektir:
- 1 - istek
Talep edilen URL'ler şu şekilde olacaktırhttp://machine:port/contexte/….?action=uneAction¶m1=v1¶m2=v2&… [Contrôleur principal], talebi doğru denetleyiciye ve bu denetleyici içindeki doğru eyleme "yönlendirmek" için bir yapılandırma dosyası kullanacaktır. Bunun için, URL'in [action] alanını kullanacaktır. URL ve [param1=v1¶m2=v2&…]'in geri kalan kısmı, eyleme iletilecek isteğe bağlı parametrelerden oluşur. Burada MVC'in C'si, [Contrôleur principal, Contrôleur / Action] dizesidir. İstenen eylemi işleyebilecek hiçbir denetleyici yoksa, web sunucusu istenen URL'in bulunamadığını bildirir.
- 2 - İşleme
- Seçilen [2a] eylemi, [Contrôleur principal] tarafından kendisine iletilen parami parametrelerini kullanabilir. Bu parametreler çeşitli kaynaklardan gelebilir:
- URL'in [/param1/param2/…] yolundan,
- URL'in [param1=v1¶m2=v2] parametrelerinden,
- tarayıcının isteğiyle birlikte gönderdiği parametrelerden;
- Kullanıcının isteğinin işlenmesi sırasında, eylem [métier] [2b] katmanına ihtiyaç duyabilir. Müşterinin isteği işlendikten sonra, bu eylem çeşitli yanıtları tetikleyebilir. Klasik bir örnek şudur:
- istek düzgün bir şekilde işlenemediğinde bir hata yanıtı;
- aksi takdirde bir onay yanıtı;
- [Contrôleur / Action], yanıtını [2c] olarak ana denetleyiciye iletecek ve bir durum kodu gönderecektir. Bu durum kodları, uygulamanın bulunduğu durumu benzersiz bir şekilde temsil edecektir. Bunlar ya başarı kodları ya da hata kodları olacaktır;
- Seçilen [2a] eylemi, [Contrôleur principal] tarafından kendisine iletilen parami parametrelerini kullanabilir. Bu parametreler çeşitli kaynaklardan gelebilir:
- 3 - yanıt
- müşterinin jSON yanıtını talep etmesine bağlı olarak, XML veya HTML yanıtını talep etmiş olmasına bağlı olarak, [Contrôleur principal], uygun yanıt türünü [3a]'te oluşturacak ve ondan yanıtı müşteriye göndermesini isteyecektir. [Contrôleur principal], çalıştırılan [Contrôleur / Action] tarafından sağlanan yanıtı ve durum kodunu ona iletecektir;
- istenen yanıt jSON veya XML türündeyse, seçilen yanıt kendisine verilen [Contrôleur / Action] yanıtını biçimlendirecek ve [3c] aracılığıyla gönderecektir. Bu yanıtı işleyebilen istemci, bir PHP konsol komut dosyası veya bir HTML sayfasında barındırılan bir JavaScript komut dosyası olabilir;
- istenen yanıt HTML türündeyse, seçilen yanıt kendisine verilen durum kodunu kullanarak [3b], HTML veya [Vuei] görünümlerinden birini seçecektir. Bu, MVC'in V'sidir. Her durum koduna tek bir görünüm karşılık gelir. Bu V görünümü, yürütülen [Contrôleur / Action]'in yanıtını görüntüleyecektir. Bu görünüm, HTML, CSS ve JavaScript kullanarak bu yanıtın verilerini işler. Bu verilere görünüm modeli denir. Bu, MVC'teki M harfidir. İstemci ise çoğu zaman bir tarayıcıdır;
Şimdi, MVC web mimarisi ile katmanlı mimari arasındaki bağlantıyı açıklayalım. Modele verilen tanıma göre, bu iki kavram birbiriyle ilişkili olabilir veya olmayabilir. Tek katmanlı bir MVC web uygulamasını ele alalım:

Yukarıda, her bir [Contrôleur / Action], [métier] ve [dao] katmanlarının bir kısmını içermektedir. [web] katmanında bir MVC mimarisi mevcut olsa da, uygulamanın tamamı katmanlı bir mimariye sahip değildir. Burada her şeyi tek bir katman gerçekleştirir.
Şimdi, çok katmanlı bir web mimarisini ele alalım:

[web] katmanı, MVC modelini takip etmeden uygulanabilir. Bu durumda çok katmanlı bir mimariye sahip oluruz, ancak web katmanı MVC modelini uygulamaz.
Örneğin, .NET ortamında yukarıdaki [web] katmanıyukarıdaki [web] katmanı, ASP.NET ve MVC ile uygulanabilir ve böylece MVC türünde bir [web] katmanına sahip katmanlı bir mimari elde edilir. Bu işlem tamamlandıktan sonra, bu ASP.NET ve MVC katmanını, geri kalan (iş işlevi, DAO, Sürücü) aynı şekilde korunur. Böylece, artık MVC türü olmayan bir [web] katmanına sahip katmanlı bir mimari elde ederiz.
MVC'te, M modelinin V görünümü olan c.a.d'in modeli olduğunu belirtmiştik. V görünümü tarafından görüntülenen tüm veriler. MVC'in M modeline ilişkin başka bir tanım verilmiştir:

Birçok yazar, [web] katmanının sağındaki kısmın MVC'in M modelini oluşturduğunu düşünmektedir. Belirsizlikleri önlemek için,
- [web] katmanının sağındaki her şeyi ifade ederken "alan modelinden";
- V görünümü tarafından görüntülenen verileri ifade ederken "görünüm modeli"nden;
23.2. NetBeans Proje Ağacı
NetBeans projesi için MVC modelini yansıtan bir mimari benimseyeceğiz:

- [3]: [main.php], MVC modelimizin ana denetleyicisidir. Bu, MVC'in C'sidir;
- [4]: [Controllers] dosyası ikincil denetleyicileri içerecektir. Her biri belirli bir eylemi işler. Bu eylem, URL dosyasında belirtilir; örneğin, […/main.php?action=authentifier-utilisateur]. Bu eylemle birlikte, [Contrôleur principal] ve [main.php], istenen eylemi işlemek üzere bir [Contrôleur secondaire]’i (burada [AuthentifierUtilisateurController]) seçecektir. Bu denetleyiciler aynı zamanda MVC'in C kısmının da bir parçasıdır;
- [5]: [Model] klasörü, uygulamanın [métier] ve [dao] katmanlarını içerecektir. Daha önce kabul edilen terimlere göre, bu öğeler etki alanı modelini temsil eder ve M için kabul edilen terminolojiye göre MVC'in M'sini temsil edebilir;
- [6]: [Responses] klasörü, müşteriye yanıtı göndermekle görevli sınıfları içerir. İstenen her yanıt türü için bir sınıf vardır:
- [JsonResponse]: jSON yanıtı için;
- [XmlResponse]: XML yanıtı için;
- [HtmlResponse]: HTML yanıtı için;
- [7]: [Views] dosyası, HTML yanıtı istendiğinde HTML görünümlerini içerir. Bu, MVC'in V'sidir. Bunlar, görüntülenecek verileri ileten [HtmlResponse] sınıfı tarafından etkinleştirilir. Bu veriler, görünüm şablonudur. M için benimsenen terminolojiye göre, bu veriler MVC'in M'si olabilir;
- [8]: [Utilities] klasörü yardımcı programlar içerir:
- [Logger]: bir metin dosyasına günlük kaydı yapmayı sağlayan sınıf;
- [Sendmail]: e-posta göndermeye olanak tanıyan sınıf;
- [9]: [Logs] klasörü, [logs.txt] günlük dosyasını içerir;
- [10]: [Entities] klasörü, çeşitli denetleyiciler tarafından kullanılan sınıfları içerir;
Bu dizin yapısı yardımıyla, bir istemci tarafından talep edilen bir eylemin işleme sürecini şu şekilde tanımlayabiliriz:
- [main.php], [3]'ten talebi alır;
- birkaç ön kontrol yaptıktan sonra (eylem kabul edilen eylemler arasında yer alıyor mu?), talebi bu eylemi işlemekle görevli ikincil denetleyici [4]’e iletir;
- ikincil denetleyici üzerine düşen işi yapar. Çalışması sırasında [métier], [dao] ve [5] katmanlarına ve [10] dosyasındaki varlıklara ihtiyaç duyabilir. Yan denetleyici, kendisini etkinleştiren ana denetleyiciye ([main.php]) yanıtını gönderir;
- müşterinin istediği [jSON, XML, HTML] yanıt türüne göre, ana denetleyici [main.php], [Responses] ve [6] klasörlerinden bir yanıtı etkinleştirir;
- [JsonResponse, XmlResponse] yanıtları, sırasıyla jSON veya XML yanıtını müşteriye gönderir;
- [HtmlResponse] yanıtı, [Views] ve [7] klasörlerindeki görünümlerden birini kullanarak müşteriye HTML yanıtını gönderir;
- farklı denetleyiciler, [8] klasöründeki [Logger] sınıfına erişerek [9] klasöründeki günlük dosyasına günlükleri yazar. Günlüğe kaydedilenler:
- istenen eylem;
- kontrolörün verdiği yanıt. Bu yanıt, talep edilen [jSON, XML, HTML] türü ne olursa olsun jSON formatında kaydedilir;
- Önemli bir hata (HTTP_INTERNAL_SERVER_ERROR) oluştuğunda, ana denetleyici [main.php], [8] klasöründeki [SendMail] sınıfını kullanarak yöneticiye bir e-posta gönderir;
23.3. Uygulamanın eylemleri
İstemci, web sunucusuna gerçekleştirilecek eylemi, URL ve [/main.php?action=xxx] içinde bir [action] parametresi biçiminde iletir. İzin verilen eylemler, ana denetleyici [main.php]'i yapılandıran [config.json] dosyasında listelenmiştir:
"actions":
{
"init-session": "\\InitSessionController",
"authentifier-utilisateur": "\\AuthentifierUtilisateurController",
"calculer-impot": "\\CalculerImpotController",
"lister-simulations": "\\ListerSimulationsController",
"supprimer-simulation": "\\SupprimerSimulationController",
"fin-session": "\\FinSessionController",
"afficher-calcul-impot": "\\AfficherCalculImpotController"
},
- 1. satır: jSON sözlüğünün [actions] anahtarı;
- 3-9. satırlar: [action:contrôleur] sözlüğü. Her eyleme, onu işlemekle görevli ikincil denetleyici atanmıştır;
- 3. satır: [init-session]: vergi hesaplamaları simülasyon oturumunu başlatır. Bu eylem, istenen yanıt türünü [jSON, XML, HTML] olarak belirtir;
- satır 4: Oturum türü belirlendikten sonra, müşteri [authentifier-utilisateur] eylemiyle kimlik doğrulaması yapmalıdır. Kimliği doğrulanana kadar, [init-session] hariç diğer tüm eylemler yasaktır;
- 5. satır: Kimliği doğrulandıktan sonra, müşteri [calculer-impot] eylemiyle bir dizi vergi hesaplaması yapabilir;
- 6. satır: Müşteri, istediği zaman [lister-simulations] işlemiyle yaptığı simülasyonların listesini görüntüleyebilir;
- 7. satır: [supprimer-simulation] komutuyla bunlardan bazılarını silebilir;
- satır 8: Müşteri, [fin-session] eylemi ile simülasyon oturumunu sonlandırır. Bu andan itibaren, uygulamayı kullanmak isterse yeniden kimlik doğrulaması yapması gerekecektir;
- 9. satır: HTML uygulamasında, [afficher-calcul-impot] eylemi, vergi hesaplamasına olanak tanıyan formun görüntülenmesini ister;
23.4. Web uygulamasının yapılandırılması
Uygulama, aşağıdaki jSON ve [config.json] dosyalarıyla yapılandırılır:
{
"databaseFilename": "database.json",
"rootDirectory": "C:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-12",
"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"
],
"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"
},
"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"
}
Açıklamalar
- 2. satır: Veritabanına erişim yapılandırmasını içeren jSON dosyasının adı;
- 3-39. satırlar: projenin bağımlılıklarının yapılandırması. Burada, proje dizin yapısındaki tüm PHP komut dosyaları listelenir;
- satır 40-44: uygulamayı kullanmaya yetkili kullanıcı;
- satır 46-54: uygulama yöneticisinin e-posta bilgileri;
- satır 55: günlük dosyalarının yolu;
- satır 56-65: [action => contrôleur secondaire chargé de la traiter] ilişkilendirmeleri;
- 66-70. satırlar: [type de réponse => classe Response chargée d’envoyer la réponse au client] ilişkilendirmeleri;
- 71-75. satırlar: [vue HTML => tableau des codes d’état menant à cette vue] ilişkilendirmeleri;
- 76. satır: Herhangi bir olağandışı hata oluştuğunda, [vue-erreurs] görünümü bir HTML oturumunda görüntülenir:
- jSON veya XML uygulamaları genellikle programlanmış bir istemci ile sorgulanır. İstemci, sunucuya eksik veya hatalı olabilecek parametreler iletir. Tüm denetleyiciler bu durumları işler ve istemciye hata kodları gönderir. Olası tüm hata durumları işlenmelidir;
- ancak HTML uygulaması söz konusu olduğunda durum biraz farklıdır. Normal kullanımda, web uygulaması jSON ve XML istemcilerinin olası kullanım senaryolarının yalnızca bir kısmını kullanır. Bir örnek verelim: [calculer-impot] eylemi, (bir POST tarafından gönderilen) üç POST parametresi bekler: [marié, enfants, salaire].
- URL'leri manuel olarak girebileceğimiz bir jSON istemcimiz varsa, POST yerine GET ile [calculer-impot] eylemini talep edebiliriz ya da üç parametre gerekliyken hiçbir parametre gönderilmeden POST ile talep edebiliriz, vb… jSON sunucusu tüm bu durumları işlemelidir;
- Bir web uygulaması kullanıldığında, [calculer-impot] eylemi, yukarıdaki iki durumun hiçbirinin mümkün olmadığı bir web formu üzerinden talep edilecektir: [calculer-impot] eylemi, bir POST ve üç [marié, enfants, salaire] parametresiyle çağrılacaktır. Bu parametrelerin bazıları hatalı bir değere sahip olabilir, ancak mevcut olacaklardır. Bununla birlikte, kullanıcı tarayıcıya kendisi URL kodlarını girerek bazı hataları yeniden oluşturabilir. Güvenlik amacıyla bu durumun yönetilmesi gerekir;
- ikincil bir denetleyici, web uygulamasıyla uyumsuz bir durum kodu, yani yapılandırma dosyasının 72-74. satırlarında bulunmayan bir durum kodu döndürdüğünde, her seferinde [vue-erreurs] görünümü görüntülenecektir. Eğitim amaçlı olarak bu çözümü tercih ediyoruz. Diğer bir seçenek ise hiçbir şey yapmamak ve kullanıcının sunucunun elle oluşturduğu URL isteklerine yanıt vermediğini düşünmesi için, istemcinin tarayıcısında o anda görüntülenen görünümü yeniden görüntülemek olabilir;
23.5. Araçların ve kütüphanelerin kurulumu
23.5.1. Postman
[Postman], web uygulamamızdaki çeşitli URL'leri sorgulamamızı sağlayacak araçtır. Bu araç bize şunları sağlar:
- herhangi bir URL'i kullanabiliriz: bunlar elle oluşturulmuştur;
- bir GET, POST, PUT, OPTIONS… ile web sunucusuna sorgu göndermek;
- GET veya POST parametrelerini belirtmek;
- isteğin HTTP başlıklarını belirlemek;
- jSON, XML, HTML biçiminde bir yanıt almak,
- yanıtın HTTP başlıklarına erişmek. Böylece sunucudan gelen tam HTTP yanıtına erişebiliriz;
Sorgulanan URL'leri elle oluşturduğumuz için, olası tüm hata durumlarını test edebilecek ve sunucunun nasıl tepki verdiğini görebileceğiz.
[Postman], URL ve [https://www.getpostman.com/downloads/] adreslerinde mevcuttur. Haziran 2019'da mevcut olan sürüm 7.2'dir. Bu sürümde bir sorun bulunmaktadır: sorgulanan web sunucusuna arka arkaya istekler gönderildiğinde, [Postman 7.2] istemcisi, sunucunun kendisine gönderdiği çerezleri, özellikle de oturum çerezini otomatik olarak geri göndermemektedir. Oturumu sürdürmek için, ardışık isteklerin HTTP başlıklarına oturum çerezini elle kopyalamak gerekir. Bu çok karmaşık bir işlem değildir, ancak pratik de değildir. Bu, önceki sürümlerde bulunmayan bir hatadır. Bu hatanın farkında olan [Postman] ekibi, [Postman Canary] adlı (kararsız olabilen) bir alfa sürümünde bu hatayı düzeltti; bu sürüm URL [https://www.getpostman.com/downloads/canary] adresinde mevcuttur. Burada kullanılan sürüm budur. Kurulumunu aşağıda açıklayacağız. [Postman 7.3] veya daha yeni bir kararlı sürüm mevcutsa, onu indirebilirsiniz: hata muhtemelen düzeltilmiş olacaktır.
[Postman] sürümünüzü kurun. Kurulum sırasında bir hesap oluşturmanız istenecektir: bu hesap burada gerekli değildir. [Postman] hesabı, farklı cihazları senkronize etmek ve bir cihazdaki ayarların diğerine aktarılmasını sağlamak için kullanılır. Bunların hiçbiri burada gerekli değildir.
Kurulum tamamlandığında, [Postman] şu arayüzü gösterir:

- [2-3]'te ürün ayarlarına erişilebilir;

- [6]'te, bu belgede kullanılan sürüm;
- bir hesap oluşturduysanız, bilgisayarınız ile [Postman] adlı uzak sunucu arasında bir senkronizasyon gerçekleşir. Bu durum, [Postman] projesinde her değişiklik yaptığınızda dönen [7] çarkı ile gösterilir. Bu gereksiz senkronizasyonu durdurmak için [8-9]'ten çıkış yapın;
23.5.2. Symfony / Serializer kütüphanesi
jSON ve XML'te nesneleri serileştirmek için [Symfony / Serializer] kütüphanesini kullanacağız. Bu kütüphanenin burada iki avantajı vardır:
- jSON veya XML formatlarında serileştirme konusunda kullanım açısından tutarlıdır: bu sayede, iki farklı API (Uygulama Programlama Arayüzü) kütüphanesini öğrenmek zorunda kalmazsınız;
- doğal olarak, nesnelerin öznitelikleri özel olsa bile, bunları jSON veya XML formatında serileştirebilir. Hatırlanacağı üzere, jSON'te bir nesneyi serileştirmek için, nesnenin sınıfının [\JsonSerializable] arayüzünü uygulaması gerekiyordu. Bu işlem sonucunda, sınıfın özniteliklerini anahtar olarak içeren bir ilişkisel dizinin jSON dizesi elde edilirdi. Bu jSON dizesi deserialize edildiğinde, ilk halindeki ilişkisel dizi ortaya çıkardı ve bu dizinin, serileştirilen sınıfın bir nesnesine dönüştürülmesi gerekirdi. [Symfony / Serializer] ile deserializasyon işlemi, serileştirilen sınıfın bir nesnesini hemen üretir. Bu daha basittir;
[Symfony / Serializer] kütüphanesinin belgeleri URL: [https://symfony.com/doc/current/components/serializer.html] (Haziran 2019) adresinde mevcuttur.
Bu kütüphaneyi yüklemek için bir Laragon terminali açın (bağlantı paragrafına bakın) ve aşağıdaki komutu girin:

- [1], [symfony/serializer] kütüphanesinin kurulum komutudur;
- [2], projemiz için gerekli olan başka bir kütüphane: nesnelerin serileştirilmesini sağlar;

23.6. Uygulamanın bileşenleri

[BaseEntity, Database, ExceptionImpots, TaxAdminData] varlıklar, web hizmetinin 08 sürümünden itibaren kullanılmaya başlanmıştır (bkz. bağlantı paragrafı).
[Simulation] sınıfı, vergi hesaplama simülasyonunun öğelerini kapsüllemek için kullanılacaktır:
<?php
namespace Application;
class Simulation extends BaseEntity {
// vergi hesaplama simülasyonunun özellikleri
protected $marié;
protected $enfants;
protected $salaire;
protected $impôt;
protected $surcôte;
protected $décôte;
protected $réduction;
protected $taux;
// alıcılar
public function getMarié() {
return $this->marié;
}
public function getEnfants() {
return $this->enfants;
}
public function getSalaire() {
return $this->salaire;
}
public function getImpôt() {
return $this->impôt;
}
public function getSurcôte() {
return $this->surcôte;
}
public function getDécôte() {
return $this->décôte;
}
public function getRéduction() {
return $this->réduction;
}
public function getTaux() {
return $this->taux;
}
}
Açıklamalar
- 5. satır: [Simulation] sınıfı, [BaseEntity] sınıfını genişletir ve dolayısıyla şu yöntemleri miras alır:
- [setFromArrayOfAttributes($arrayOfAttributes)]: Sınıfın özniteliklerini başlatmaya yarar;
- [__toString]: nesnenin jSON dizesini döndürür;
- 7-14. satırlar: simülasyonun öznitelikleri;
- 16-47. satırlar: sınıfın getter yöntemleri;
23.7. Uygulamanın Yardımcı Programları
![]()
[Logger] sınıfı, olayları bir metin dosyasına kaydetmeye olanak tanır. Bu sınıf, bağlantı başlığı altında açıklanmıştır.
[SendAdminMail] sınıfı, uygulama yöneticisine e-posta gönderilmesini sağlar. Bu sınıf, bağlantı başlığı altında açıklanmıştır.
23.8. [métier] ve [dao] katmanları


[métier] ve [dao] katmanlarının sınıfları ve arayüzleri, [Model] klasöründe toplanmıştır. Bunların tümü önceki sürümlerde tanımlanmış ve kullanılmıştır:
ExceptionImpots | [dao] katmanı tarafından atılan istisnaların sınıfı. Bağlantı bölümünde tanımlanmıştır. |
InterfaceServerDao | Sunucunun [dao] katmanı tarafından uygulanan arayüz. Bağlantı paragrafında tanımlanmıştır. |
ServerDao | [InterfaceServerDao] arayüzünün uygulaması. Sunucunun [dao] katmanını uygular. Bağlantı paragrafında tanımlanmıştır. |
ServerDaoWithSession | [InterfaceServerDao] arayüzünün uygulaması. Sunucunun [dao] katmanını uygular. Bağlantı paragrafında tanımlanmıştır. |
InterfaceServerMetier | Sunucunun [métier] katmanı tarafından uygulanan arayüz. “Bağlantı” başlığı altında tanımlanmıştır. |
ServerMetier | [InterfaceMetier] arayüzünün uygulaması. Sunucunun [metier] katmanını uygular. "Bağlantı" paragrafında tanımlanmıştır. |
Yazılmakta olan uygulama, daha önce tanıtılmış ve kullanılmış birçok öğeyi kullanmaktadır:
- [métier] ve [dao] katmanları;
- [Logger] ve [SendAdminMail] yardımcı programları;
- [ExceptionImpots, TaxAdminData, Database] varlıklar;
Uygulamanın [web] katmanına odaklanacağız:

23.9. Ana denetleyici [main.php]
23.9.1. Giriş

- [1-2]: [main.php] [1] ana denetleyicisi, [config.json] [2] dosyası tarafından yapılandırılır;
MVC mimarimizdeki ana denetleyicinin konumunu hatırlayalım:

[1]'te, ana denetleyici [main.php], MVC mimarisinde istemcinin isteğini işleyen ilk elemandır. Birkaç rolü vardır:
- öncelikle temel kontrolleri gerçekleştirir:
- yapılandırma dosyası mevcut mu ve geçerli mi;
- projenin tüm bağımlılıklarını yükler. Bu, MVC mimarisinin tüm bileşenlerini yüklemek anlamına gelir;
- istenen eylem belirtilmiş mi? Eğer öyleyse, geçerli mi?
- talep edilen eylem geçerliyse, [2a]'i seçer; bu, eylemi işleyecek ikincil denetleyicidir ve ona ihtiyaç duyduğu bilgileri iletir: HTTP isteği, oturum, uygulama yapılandırması;
- ikincil denetleyiciden gelen yanıtı ([2c]) alın. Türüne göre (jSON, XML, HTML) seçin ve yanıtı (JsonResponse, XmlResponse, HtmlResponse) seçin ve müşteriye yanıtı göndermekle görevli bu uygulamaya, ihtiyaç duyduğu tüm bilgileri (HTTP isteği, oturum, uygulama yapılandırması, ikincil denetleyicinin yanıtı) aktarın;
- bu yanıt ([3c]) gönderildikten sonra, isteğin işlenmesi için kullanılmış olabilecek kaynakları serbest bırakmak;
23.9.2. [main.php] - 1
Ana denetleyicinin kodu [main.php] şu şekildedir:
<?php
// fonksiyon parametrelerinin beyan edilen türlerine sıkı uyum
declare (strict_types=1);
// ad alanı
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
// PHP ile hata yönetimi
//ini_set("display_errors", "0");
error_reporting(E_ALL && !E_WARNING && !E_NOTICE);
// yapılandırma alınır
$configFilename = "config.json";
$fileContents = \file_get_contents($configFilename);
$erreur = FALSE;
// hata mı?
if (!$fileContents) {
// hatayı kaydediyoruz
$état = 131;
$erreur = TRUE;
$message = "Le fichier de configuration [$configFilename] n'existe pas";
}
if (!$erreur) {
// yapılandırma dosyasından JSON kodunu bir ilişkisel diziye alıyoruz
$config = \json_decode($fileContents, true);
// hata mı?
if (!$config) {
// hata kaydediliyor
$erreur = TRUE;
$état = 132;
$message = "Le fichier de configuration [$configFilename] n'a pu être exploité correctement";
}
}
// hata mı?
if ($erreur) {
// sunucudan gelen JSON yanıtının hazırlanması
// yapılandırma dosyasından yararlanamıyoruz
// Symfony bağımlılıkları
require_once "C:/myprograms/laragon-lite/www/vendor/autoload.php";
// yanıtın hazırlanması
$response = new Response();
$response->headers->set("content-type", "application/json");
$response->setCharset("utf-8");
// durum kodu
$response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
// içerik
$response->setContent(json_encode(["action" => "", "état" => $état, "réponse" => $message], JSON_UNESCAPED_UNICODE));
// gönderme
$response->send();
// son
exit;
}
…
Açıklamalar
- 10-12. satırlar: ana denetleyici, Symfony'nin aşağıdaki nesnelerini kullanır:
- [Request]: işlenmekte olan HTTP isteği;
- [Session]: web uygulamasının oturumu;
- [Response]: müşteriye gönderilen HTTP yanıtı;
- 15. satır: Geliştirme süreci boyunca bu satırı yorum olarak bırakacağız: PHP hataları, müşteriye gönderilen metin akışına dahil edilir. Müşteri bir tarayıcıysa, bu sayede sunucunun karşılaştığı hataları görebiliriz. Bu, hata ayıklamaya yardımcı olur;
- 16. satır: Uyarılar (! E_WARNING) ve kritik olmayan bilgiler (! E_NOTICE) hariç, tüm hatalar bildirilir (E_ALL). Örneğin, bir dosya açılamazsa, PHP, [E_NOTICE] türünde bir hata verir. 15. satır hataların görüntülenmesine izin veriyorsa, dosya açma hatası istemci tarayıcısında görünür. Dosyanın açılma sonucunu test etmeyi unuttuysanız bu iyi bir durumdur, ancak testi planladıysanız durum o kadar da iyi değildir: bu durumda bir [notice] satırı, sunucunun istemciye verdiği yanıtı bozar. Geliştirme aşamasında, 16. satır da yorumlanmalıdır: hiçbir hatayı kaçırmak istemezsiniz;
- 19. satır: yapılandırma dosyası okunur;
- 22-27. satırlar: okuma işlemi başarısız olursa, hata kaydedilir (25. satır), uygulama [131] durumuna geçirilir ve bir hata mesajı hazırlanır;
- 30. satır: Yapılandırma dosyasındaki jSON dizesi çözülür;
- satır 32-37: Bu kod çözme işlemi başarısız olursa, hatayı kaydederiz (satır 34), uygulamayı [132] durumuna geçiririz ve bir hata mesajı hazırlarız;
- satır 40-57: Yapılandırma dosyasının okunmasında hata oluşursa, işlem devam edemez. Bu durumda, istemciye jSON yanıtı hazırlanır:
- satır 44: Yapılandırma dosyası okunamadığı için, [Symfony] için gerekli olan [autoload] dosyasını manuel olarak içe aktarmak gerekir;
- 46-47. satırlar: jSON yanıtı hazırlanır;
- 50. satır: Yanıtın HTTP kodu 500 INTERNAL_SERVER_ERROR olacaktır;
- satır 52: yanıtın içeriği jSON olarak belirlenir. İncelenen web uygulaması tarafından verilen tüm yanıtlar üç anahtara sahip olacaktır:
- [action]: istemci tarafından talep edilen eylem;
- [état]: bu eylemin yürütülmesinden sonra uygulamanın durumu;
- [réponse]: web sunucusunun yanıtı;
- 54. satır: jSON yanıtı istemciye gönderilir;
23.9.3. [Postman] Testleri - 1
Yapılandırma dosyası bulunmadığında veya hatalı olduğunda sunucunun davranışını kontrol edeceğiz:

[Postman] istemcimizin vergi sunucusuna göndereceği çeşitli istekleri koleksiyonlar halinde toplayacağız.
- [1]'te yeni bir koleksiyon oluşturun;
- [2]'te koleksiyona bir ad verin;
- [3]'te açıklama isteğe bağlıdır;

- [4] koleksiyonlarında, artık [impots-server-tests-version12] [5] adlı bir koleksiyon görünür;
- [6]'te, koleksiyona yeni bir sorgu eklenebilir;

- [7]'te, sorguya bir ad verilir;
- [8]'te açıklama isteğe bağlıdır;

- [9-11]'te, sorgu koleksiyona eklenir;
- [12]'te, sorgu türünün seçimi yapılır; burada bir [GET] sorgusu. [19]'te, mevcut farklı sorgu türleri;
- [13]'te, buraya sunucunun URL'i girilir;
- [14]'te, URL'e eklenen ve dolayısıyla GET'in parametreleri olacak parametreler buraya girilir. Bunları doğrudan URL dosyasına değil de buraya eklemenin avantajı, bunların [Postman] tarafından URL kodlamasına tabi tutulacak olmasıdır. Bunları URL dosyasına kendiniz eklerseniz, URL kodlamasını sizin yapmanız gerekecektir;
- [15]'te, [Authorization], oturum açacak kullanıcıyı tanımlamak için kullanılır. Bu seçeneği kullanmamıza gerek kalmayacak;
- [16]'te, isteğe eşlik edecek HTTP başlıkları yer alır. Bazı başlıklar isteğe otomatik olarak eklenir. Buradan yeni başlıklar ekleyebilirsiniz;
- [17] ve [Body], [POST] işlemine ait parametreleri belirtir. Bu seçeneği kullanmamız gerekecek;
Şu testi yapacağız:
- [main.php]'te, yapılandırma dosyasının [config2.json] olduğu belirtiliyor, ancak bu dosya mevcut değil:

- kodun 16. satırındaki yorum işareti kaldırılmalıdır;
- 18. satır: yapılandırma dosyasının adıyla ilgili hata;
[Postman], [13, 20] ve vergi hesaplama web sunucusundaki URL dosyalarına girelim ve [21] dosyasını çalıştıralım:

Sunucu tarafından gönderilen yanıt (tabii ki Laragon'un aktif olması gerekir) şöyledir:

- [22]'te, sunucu HTTP [500 Internal Server Error] kodunu geri gönderdi;
- [23]'te, [Body] yanıtın gövdesini, yani HTTP ve [28] başlıklarının arkasında sunucu tarafından gönderilen belgeyi belirtir;
- [26]'te, [Postman]'in jSON yanıtını aldığı görülür;
- [27]'te, biçimlendirilmiş jSON yanıtı;
- [28]'te, biçimlendirilmemiş ham jSON yanıtı;
- [29]'e, yanıt HTML olduğunda [Preview] modu kullanılır. Bu durumda [Preview] modu alınan sayfayı görüntüler;
- [30]'te, sunucudan gelen jSON yanıtı. Bu, tam da beklediğimiz yanıt;
[25] modunda, sunucunun yanıtında gönderilen HTTP başlıkları şunlardır:

- [32]'te, yanıtın türü jSON;
Bu ilk test sayesinde şunu görebildik:
- test edilen sunucuya her türlü istek gönderilebileceğini;
- GET veya POST parametrelerini ayarlayabileceğimizi;
- yanıtın tamamına sahip olduğumuzu: HTTP başlıkları ve bu başlıkların ardından gelen [Body] belgesi;
Şimdi ikinci bir test yapalım:

- [1-3]'e dönüştürüldüğünde, [config3.json] dosyası, sözdizimi açısından hatalı bir jSON dosyasıdır;
- [4]'e dönüştürürken, [main.php], [config3.json]'i kullanacak şekilde yapılandırılmıştır;
[Postman] dosyasına yeni bir sorgu ekliyoruz:

- [1-3]'te, [2]'e sağ tıklayıp [duplicate] seçeneğini seçerek [2] sorgusunu kopyalıyoruz;
- [4]'te, yeni sorgunun önceden tanımlanmış adını [5] olarak değiştirin;

- [6]'e, yeniden adlandırılan istek;
- [9-10]'e, daha önce gönderilen GET isteğinin aynısı gönderilir;

- [11]'te, sunucudan gelen jSON yanıtı;
Burada, vergi hesaplama web hizmetinin çeşitli işlemlerinin nasıl test edileceğini gösterdik.
23.9.4. [main.php] – 2
Ana denetleyicinin kodunu incelemeye devam ediyoruz: [main.php]:
<?php
// fonksiyon parametrelerinin beyan edilen türlerine sıkı sıkıya uyulması
declare (strict_types=1);
// ad alanı
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
// PHP ile hata yönetimi
//ini_set("display_errors", "0");
error_reporting(E_ALL && !E_WARNING && !E_NOTICE);
// yapılandırma alınır
$configFilename = "config.json";
…
// komut dosyası için gerekli bağımlılıkları ekliyoruz
$rootDirectory = $config["rootDirectory"];
foreach ($config["relativeDependencies"] as $dependency) {
require_once "$rootDirectory$dependency";
}
// mutlak bağımlılıklar (üçüncü taraf kütüphaneler)
foreach ($config["absoluteDependencies"] as $dependency) {
require_once "$dependency";
}
// günlük dosyasının oluşturulması
try {
$logger = new Logger($config['logsFilename']);
} catch (ExceptionImpots $ex) {
// günlük dosyası oluşturulamadı - dahili sunucu hatası
$état = 133;
(new JsonResponse())->send(
NULL, NULL, $config,
Response::HTTP_INTERNAL_SERVER_ERROR,
["action" => "non déterminée", "état" => $état, "réponse" => "Le fichier de logs [{$config['logsFilename']}] n'a pu être créé"],
[]);
// işlem tamamlandı
exit;
}
Yorumlar
- 18. satır: Artık mevcut ve sözdizimi açısından doğru bir [config.json] yapılandırma dosyası var. Ayrıca, bu dosyada beklenen anahtarların gerçekten mevcut olup olmadığını da test etmek gerekir. Bunu, geliştiricinin normal hata ayıklama işinin bir parçası olarak kabul edeceğiz. Aynı mantığı önceki iki hata için de uygulayabilirdik;
- 20-28. satırlar: Web projesi için gerekli tüm bağımlılıkları dahil ediyoruz. Bu kodla daha önce birkaç kez karşılaştık;
- 31-43. satırlar: [$config['logsFilename']] dosyasına olayları kaydetmemizi sağlayacak [Logger] nesnesini oluşturmaya çalışıyoruz. Bu oluşturma işlemi başarısız olabilir;
- 33-43. satırlar: [Logger] nesnesinin oluşturulmasında meydana gelen hatanın yönetimi;
- 35. satır: bir durum numarası belirlenir;
- satır 36-40: jSON yanıtı gönderilir;
- satır 42: komut dosyası durdurulur;
Müşteriye gönderilen tüm yanıtlar aşağıdaki [InterfaceResponse] arayüzünü uygular:

[InterfaceResponse] arayüzünün kodu şöyledir:
<?php
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
interface InterfaceResponse {
// $request isteği: istek işleniyor
// $session oturumu: web uygulamasının oturumu
// $config dizisi: uygulama yapılandırması
// int statusCode : yanıt durum kodu HTTP
// dizi $content : sunucu yanıtı
// dizi $headers : yanıtta eklenecek HTTP başlıkları
// Logger $logger: günlükleri yazmak için kullanılan günlük kaydedici
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void;
}
- 19-27. satırlar: [InterfaceResponse] arayüzü, yanıtı istemciye göndermek için tek bir [send] yöntemine sahiptir;
- 11-17. satırlar: [send] yöntemindeki çeşitli parametrelerin anlamları;
- satır 23-25: [$statusCode, $content, $headers] parametreleri, uygulamanın ikincil denetleyicilerinin standart çıktısında yer alır. Ancak yanıtın başka bilgilere de ihtiyacı olabilir. Bu nedenle, isteğe, oturuma ve yapılandırmaya ilişkin tüm bilgilere erişimini sağlayan ilk üç parametre (satır 20-22) de yanıtta yer alır;
- satır 26: Yanıt, müşteriye gönderilen yanıtı günlüğe kaydedeceği için [Logger]'e ihtiyaç duyar;
[JsonResponse] sınıfı, [InterfaceResponse] arayüzünü şu şekilde uygular:
<?php
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
class JsonResponse extends ParentResponse implements InterfaceResponse {
// $request isteği: istek işleniyor
// $session oturumu: web uygulamasının oturumu
// $config dizisi: uygulama yapılandırması
// int statusCode : yanıt durum kodu HTTP
// dizi $content : sunucu yanıtı
// dizi $headers : yanıtta eklenecek HTTP başlıkları
// Logger $logger: günlükleri yazmak için kullanılan günlük kaydedici
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void {
// Symfony serileştiricisinin hazırlanması
$serializer = new Serializer(
[
// nesnelerin serileştirilmesi için gereklidir
new ObjectNormalizer()],
// jSON kodlayıcı
// seçenekler için, farklı seçenekler arasına OU ekleyin
[new JsonEncoder(new JsonEncode([JsonEncode::OPTIONS => JSON_UNESCAPED_UNICODE]))]
);
// serileştirme jSON
$json = $serializer->serialize($content, 'json');
// başlıklar
$headers = array_merge($headers, ["content-type" => "application/json"]);
// yanıt gönderme
parent::sendResponse($statusCode, $json, $headers);
// günlük
if ($logger !== NULL) {
$logger->write("réponse=$json\n");
}
}
}
Açıklamalar
- 13. satır: Sınıf, [InterfaceResponse] arayüzünü uygular;
- 13. satır: Sınıf, [ParentResponse] sınıfını genişletir. Tüm [Response] türleri bu sınıfı genişletir. İstemciye yanıtı gönderen bu üst sınıftır (46. satır). Bu kod, tüm [Response] türleri için ortak olduğundan, bir üst sınıfa faktörleştirilmiştir;
- satır 33-40: [Symfony] serileştiricisinin örneklenmesi; bu serileştirici, [$content] sunucusunun yanıtını jSON dizesine dönüştürecektir (satır 42);
- satır 34-36: [Serializer] oluşturucusunun ilk parametresi bir dizidir. Bu diziye, nesnelerin serileştirilmesi için gerekli olan [ObjectNormalizer] sınıfının bir örneği eklenir. Bu durum, bu uygulamada her bir simülasyonun [Simulation] sınıfının bir örneği olduğu bir simülasyon listesiyle karşımıza çıkar;
- 39. satır: [Serializer] yapıcı işlevinin ikinci parametresi de bir dizidir: buraya bir serileştirmede kullanılan tüm kodlayıcılar eklenir (XML, jSON, CSV…);
- satır 39: burada sadece [JsonEncoder] türünde bir kodlayıcı olacaktır. Parametresiz oluşturucu yeterli olabilirdi. Burada, yalnızca jSON kodlama seçeneklerini aktarmak için oluşturucuya [JsonEncode] parametresini aktardık;
- satır 39: [JsonEncode] oluşturucu parametresi bir seçenekler dizisidir. Burada, jSON dizgesindeki UTF-8 karakterlerinin "kaçış karakterleri" kullanılmadan doğal olarak görüntülenmesini talep etmek için [JSON_UNESCAPED_UNICODE] seçeneği kullanılıyor;
- 42. satır: HTTP yanıt gövdesi, önceki serileştirici sayesinde jSON olarak serileştirilir;
- 44. satır: HTTP başlığı eklenir; bu başlık, müşteriye jSON formatında bir veri gönderileceğini bildirir;
- 46. satır: üst sınıftan yanıtı müşteriye göndermesini isteriz;
- 48-50. satırlar: jSON yanıtını günlüğe kaydediyoruz;
[ParentResponse] üst sınıfının kodu şöyledir:
<?php
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Response;
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(
int $statusCode,
string $content,
array $headers): void {
// sunucudan gelecek metin yanıtının hazırlanması
$response = new Response();
$response->setCharset("utf-8");
// durum kodu
$response->setStatusCode($statusCode);
// başlıklar
foreach ($headers as $text => $value) {
$response->headers->set($text, $value);
}
// yanıt gönderiliyor
$response->setContent($content);
$response->send();
}
}
Açıklamalar
- satır 10-13: [send] yönteminin üç parametresinin anlamı;
- 17. satır: Yanıt gövdesinin [string] türünde olduğu ve dolayısıyla gönderilmeye hazır olduğu (30. satır) dikkat çekmektedir;
- 22. satır: Yanıtta UTF-8 karakterleri yer alacaktır;
- 24. satır: yanıtın durum kodu HTTP;
- 26-28. satırlar: arayan kodu tarafından verilen HTTP başlıklarının eklenmesi;
- satır 30-31: yanıtın müşteriye gönderilmesi;
jSON yanıtının tüm döngüsünü ayrıntılı olarak açıkladık. Bundan sonra bu konuya tekrar değinmeyeceğiz. Sadece [InterfaceResponse] arayüzünün imzasını hatırlamak yeterlidir:
interface InterfaceResponse {
// $request isteği: istek işleniyor
// Oturum $session: web uygulamasının oturumu
// dizi $config: uygulama yapılandırması
// int statusCode : yanıt durum kodu HTTP
// dizi $content : sunucu yanıtı
// dizi $headers : yanıta eklenecek HTTP başlıkları
// Logger $logger: günlükleri yazmak için kullanılan günlük kaydedici
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void;
}
Ana denetleyici [main.php], müşteriye yanıtın gönderilmesini her talep ettiğinde bu imzaya uymak zorundadır.
23.9.5. [Postman] Testleri – 2
[config.json] dosyasını şu şekilde değiştiriyoruz:

- [1] dosyasında, günlük dosyasının [Logs] olduğunu ve bunun da [2] adlı bir klasör olduğunu belirtiyoruz. Dolayısıyla [Logs] dosyasının oluşturulması başarısız olmalıdır;
[Postman] [3] adlı yeni bir istek oluşturuyoruz; buna [erreur-133] adını veriyoruz:

- [2-4]: önceki iki testte olduğu gibi aynı isteği tanımlıyoruz;
- [5-7]: Beklenen jSON yanıtını başarıyla alıyoruz;
23.9.6. [main.php] – 3
Ana denetleyici [main.php]'i incelemeye devam edelim:
<?php
// fonksiyon parametrelerinin beyan edilen türlerine sıkı sıkıya uyulması
declare (strict_types=1);
// ad alanı
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
// PHP ile hata yönetimi
…
// günlük dosyasının oluşturulması
…
// 1. günlük
$logger->write("\n---nouvelle requête\n");
// mevcut istek
$request = Request::createFromGlobals();
// oturum
$session = new Session();
$session->start();
// hata listesi
$erreurs = [];
$erreur = FALSE;
// istenen eylem işleniyor
if (!$request->query->has("action")) {
$erreurs[] = "paramètre [action] manquant";
$erreur = TRUE;
$état = 101;
$action = "";
} else {
// eylem kaydediliyor
$action = strtolower($request->query->get("action"));
}
// eylem günlüğe kaydediliyor
$logger->write("action [$action] demandée\n");
// eylem mevcut mu?
if (!$erreur && !array_key_exists($action, $config["actions"])) {
$erreurs[] = "action [$action] invalide";
$erreur = TRUE;
$état = 102;
}
// Bazı işlemler yapılmadan önce oturum türü bilinmelidir
if (!$erreur && !$session->has("type") && $action !== "init-session") {
$erreurs[] = "pas de session en cours. Commencer par action [init-session]";
$erreur = TRUE;
$état = 103;
}
// Bazı işlemler için kimlik doğrulaması yapılmalıdır
if (!$erreur && !$session->has("user") && $action !== "authentifier-utilisateur" && $action !== "init-session") {
$erreurs[] = "action demandée par utilisateur non authentifié";
$erreur = TRUE;
$état = 104;
}
// Hatalar mı var?
if ($erreurs) {
// Yanıt gönderilmeden hazırlanıyor
$statusCode = Response::HTTP_BAD_REQUEST;
$content = ["réponse" => $erreurs];
$headers = [];
} else {
// ---------------------------
// eylem, ilgili denetleyici kullanılarak yürütülür
$controller = __NAMESPACE__ . $config["actions"][$action];
$logger->write("contrôleur : $controller\n");
list($statusCode, $état, $content, $headers) = (new $controller())->execute($config, $request, $session);
}
// --------------------- yanıt gönderiliyor
// ölümcül hata durumu HTTP_INTERNAL_SERVER_ERROR
// mümkünse yöneticiye bir e-posta gönderilir
if ($statusCode === Response::HTTP_INTERNAL_SERVER_ERROR && $config['adminMail'] != NULL) {
$infosMail = $config['adminMail'];
$infosMail['message'] = json_encode($content, JSON_UNESCAPED_UNICODE);
$sendAdminMail = new SendAdminMail($infosMail, $logger);
$sendAdminMail->send();
}
// yanıt, oturum türüne bağlıdır
if ($session->has("type")) {
// oturum türü oturumda bulunur
$type = $session->get("type");
} else {
// oturumda tür yoksa, varsayılan olarak jSON biçiminde bir yanıt verilir
$type = "json";
}
// denetleyicinin yanıtına [action, état] anahtarları eklenir
$content = ["action" => $action, "état" => $état] + $content;
// yanıtı istemciye göndermekle görevli [Response] nesnesi oluşturulur
$response = __NAMESPACE__ . $config["types"][$type]["response"];
(new $response())->send($request, $session, $config, $statusCode, $content, $headers, $logger);
// yanıt gönderildi - kaynaklar serbest bırakılır
$logger->close();
exit;
Yorumlar
- İlk kontroller yapıldıktan ve çalışabileceğini anladıktan sonra, ana denetleyici kendisine istenen eyleme odaklanır: bu eylemin belirli koşulları yerine getirmesi gerekir;
- 21. satır: Yeni bir istek geldiği kaydedilir. Daha önce bunu yapamazdık çünkü geçerli bir günlük dosyasına sahip olduğumuzdan emin değildik;
- 23. satır: Müşterinin isteğindeki tüm bilgileri Symfony [Request] nesnesine yerleştirilir;
- 26. satır: Yeni bir oturum başlatıyoruz; mevcut bir oturum varsa onu alıyoruz;
- 27. satır: Oturum etkinleştirilir;
- 29. satır: bir hata mesajları dizisi;
- satır 30: testler sırasında bir hata ile karşılaşıp karşılaşmadığımızı bize bildiren bir boole değeri;
- satır 32: [action] parametresi, URL'in bir parçası olmalı ve [main.php?action=uneAction] biçiminde olmalıdır. Bu durumda [action] parametresi, [$request→query] parametresinin bir parçasıdır;
- satır 33-36: URL içinde [action] parametresinin bulunmaması durumu. Hata kaydedilir ve ona [101] durumu atanır;
- satır 39: [action] parametresi URL'te mevcutsa, bu parametre kaydedilir;
- 42. satır: Eylem türü günlüğe kaydedilir;
- 45-49. satırlar: [action] parametresi mevcutsa, geçerli olmalıdır. İzin verilen tüm eylemler [$config["actions"]] ilişkisel tablosunda tanımlanmıştır;
- satır 46-48: Eylem geçersizse, hata kaydedilir ve eyleme [102] durumu atanır;
- satır 52-56: Geçerli bir eylem söz konusudur. Bu eylemin başka koşulları da yerine getirmesi gerekir. Web uygulaması üç tür yanıt sağlar (jSON, XML, HTML). Bu tür, [init-session] eylemi tarafından belirlenir. Bu eylem, oturum türünü [type] anahtarına yerleştirir;
- 52. satır: [init-session] eylemi dışında, oturumda [type] anahtarı ile başka herhangi bir eylemin gerçekleştirilmesi gerekir;
- satır 53-55: Aksi takdirde, hata kaydedilir ve oturuma [103] durumu atanır;
- satır 58-63: [init-session] ve [authentifier-utilisateur] eylemleri dışında, diğer tüm eylemler kimlik doğrulamasından sonra gerçekleştirilmelidir. Kimlik doğrulama, [authentifier-utilisateur] eylemi kullanılarak gerçekleştirilir; kimlik doğrulama başarılı olursa, oturuma bir [user] anahtarı eklenir;
- 59. satır: İşlem ne [init-session] ne de [authentifier-utilisateur] ise ve [user] anahtarı oturumda bulunmuyorsa, bir hata oluşur;
- 60-62. satırlar: hata kaydedilir ve ona [104] durumu atanır;
- 66-71. satırlar: [$erreurs] dizisinin boş olup olmadığı kontrol edilir. Boş değilse, istenen eylem veya yürütme bağlamı hatalıdır;
- 68-70. satırlar: Müşteriye gönderilecek yanıt hazırlanır, ancak henüz gönderilmez;
- satır 68: durum kodu HTTP;
- 69. satır: yanıtın gövdesi;
- satır 70: yanıta eklenecek başlıklar, burada yok;
- satır 73: geçerli bir eylem var. Bu eylemi işlemek üzere (ikincil) denetleyiciden talep edilecek;
- satır 74: çalıştırılacak denetleyicinin sınıf adı oluşturulur. [__NAMESPACE__], içinde bulunduğumuz ad alanıdır; burada [Application] (satır 7);
- ikincil denetleyici sınıflarının adları [config.json] dosyasında yer almaktadır:
"actions":
{
"init-session": "\\InitSessionController",
"authentifier-utilisateur": "\\AuthentifierUtilisateurController",
"calculer-impot": "\\CalculerImpotController",
"lister-simulations": "\\ListerSimulationsController",
"supprimer-simulation": "\\SupprimerSimulationController",
"fin-session": "\\FinSessionController",
"afficher-calcul-impot": "\\AfficherCalculImpotController"
},
Her eyleme bir ikincil denetleyici karşılık gelir. Eylem [authentifier-utilisateur] ise, 74. satırdaki [$controller] değişkeni [Application/AuthentifierUtilisateurController] değerini alır;
- 75. satır: geliştirme aşamasında doğrulama amacıyla ikincil denetleyicinin adı günlüğe kaydedilir;
- 76. satır: ikincil denetleyici çalıştırılır. İkincil denetleyicilere biraz ileride tekrar değineceğiz;
- 76. satır: Tüm ikincil denetleyiciler, bir dizi olan aynı türde sonuç döndürür:
- [$statusCode] dizisinin ilk elemanı, gönderilecek yanıtın durum kodu olan HTTP'tir;
- ikinci eleman [$état], denetleyicinin yürütülmesinden sonra uygulamanın durumudur;
- üçüncü eleman [$content], müşteriye gönderilecek yanıtın gövdesini içeren ve [réponse] anahtarına sahip bir ilişkisel dizidir;
- dördüncü öğe [$headers], müşteriye gönderilen yanıta eklenecek HTTP başlık dizisidir;
- 79. satır: buraya şu şekilde ulaşılır:
- ya bir hata olduğu için (68-70. satırlar);
- ya da bir denetleyicinin çalıştırılmasından sonra (satır 72-76);
- her iki durumda da, müşteriye verilecek yanıtın oluşturulması için gerekli olan [$statusCode, $état, $content, $headers] öğeleri bilinmektedir;
- satır 82-87: [500 Internal Server Error] durum kodunun özel durumunu ele alır. Bir denetleyici bu durum kodunu ayarladıysa, bu durum uygulamanın çalışamadığı anlamına gelir. Örneğin, kullanılan SGBD başlatılmamışsa veya artık yanıt vermiyorsa, vergi hesaplaması bu duruma girer. Bu durumda, uygulama yöneticisine bir e-posta gönderilerek durum bildirilir. Bu kod hakkında özel bir açıklama yapmayacağız. [SendAdminMail] sınıfının kullanımı daha önce açıklanmıştır (bağlantı paragrafı);
- 89-95. satırlar: Web uygulamasının [jSON, XML, HTML] türü belirlenir. [init-session] eylemi başarıyla gerçekleştirildiyse, bu tür, [type] anahtarıyla ilişkili oturumda bulunur (91. satır). Aksi takdirde, yanıt için rastgele bir tür belirlenir: jSON türü (94. satır);
- 97. satır: [$content], tek bir anahtar ([réponse]) ve tek bir değer (müşteriye gönderilecek yanıt gövdesi) içeren bir dizidir. Buna [action] ve [état] anahtarları eklenir. [action] anahtarı, [logs.txt] dosyasının günlüklerini daha iyi takip etmeyi sağlayacaktır. [état] anahtarının iki işlevi olacaktır:
- jSON ve XML istemcilerinin, yürütülen eylemin web uygulamasını hangi duruma getirdiğini öğrenmelerini sağlayacak;
- bir HTML yanıtı durumunda, istemci tarayıcısına gönderilmesi gereken HTML görünümünün seçilmesini sağlayacaktır;
- 99. satır: Müşteriye yanıtı göndermek için yürütülecek [Response] sınıf türünü seçeriz;
[JsonResponse] sınıfını daha önce "bağlantı" başlığı altında tanıtmıştık. Bu sınıf, [InterfaceResponse] arayüzünü uygular ve [ParentResponse] sınıfını genişletir. Diğer iki sınıf olan [XmlResponse] ve [HtmlResponse] için de durum aynıdır.
Yanıtlar, [Responses] klasöründe toplanmıştır:

Tüm bu sınıflar, bağlantı paragrafında da tanıtılan [InterfaceResponse] arayüzünü uygulamaktadır:
<?php
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
interface InterfaceResponse {
// $request isteği: istek işleniyor
// $session oturumu: web uygulamasının oturumu
// $config dizisi: uygulama yapılandırması
// int statusCode : yanıt durum kodu HTTP
// dizi $content : sunucu yanıtı
// dizi $headers : yanıtta eklenecek HTTP başlıkları
// Logger $logger: günlükleri yazmak için kullanılan günlük kaydedici
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void;
}
Bu arayüz, yanıtı istemciye göndermekle görevli tek bir yöntem olan [send]'e sahiptir. Bu yöntem, 11-17. satırlarda açıklanan 7 parametreye sahiptir. [Responses] klasöründeki tüm sınıflar ve arayüzler, [Application] ad alanında yer almaktadır (3. satır).
[main.php] koduna geri dönelim:
…
// denetleyicinin yanıtına [action, état] anahtarları eklenir
$content = ["action" => $action, "état" => $état] + $content;
// Müşteriye yanıtı göndermekle görevli [Response] nesnesi oluşturulur
$response = __NAMESPACE__ . $config["types"][$type];
(new $response())->send($request, $session, $config, $statusCode, $content, $headers, $logger);
// yanıt gönderildi - kaynaklar serbest bırakılır
$logger->close();
exit;
- 5. satır: Uygulamanın türüne uygun [Response] sınıfı örneklenir. Bu sınıflar, [config.json] dosyasında şu şekilde tanımlanmıştır:
"types": {
"json": "\\JsonResponse",
"html": "\\HtmlResponse",
"xml": "\\XmlResponse"
},
- 5. satır: Sınıf adının önüne ad alanı öneki eklenir;
- 6. satır: [Response] sınıfı örneklenir ve [send] yöntemi, beklenen 7 parametre ile çağrılır. Bu parametreler, tüm yanıt sınıflarının uyguladığı [InterfaceResponse] arayüzüne aittir. Bu işlem, yanıtı istemciye gönderir;
- 9. satır: Günlük dosyası kapatılır;
- 10. satır: ana denetleyici işini tamamladı;
23.9.7. [Postman] Testleri – 3
URL'in [action] parametresine ilişkin çeşitli hata durumlarını test edeceğiz.

- [1]'te:
- [erreur-101]: URL'te [action] parametresinin eksik olması durumu;
- [erreur-102]: URL dosyasında bulunan ancak tanınmayan [action] parametresi durumu;
- [erreur-103]: URL içinde bulunan [action] parametresinin durumu; parametre tanınmış, ancak beklenen yanıt türü [json, xml, html] tanımlanmamıştır;
Her istek yürütülür. Elde edilen sonuçları doğrudan sunuyoruz:
Yukarıda:
- [2-4]'te, URL ve [4]'te [action] parametresi bulunmayan bir sorgu;
- [5-7]'te, jSON sonucu;

Yukarıda:
- [5-9]'te, geçersiz bir [action] parametresine sahip bir istek;
- [10-13]'te, jSON yanıtı;

Yukarıda:
- [14-19]'te, eylem tanındı ancak türü (json, xml, html) henüz belirtilmedi;
- [20-23]'te, sunucudan gelen jSON yanıtı;
23.10. İkincil denetleyiciler
Her eylem, [Controllers] klasöründeki denetleyicilerden biri tarafından yürütülür:


Yukarıdaki uygulamanın genel mimarisinde, ikincil denetleyiciler [2a]'tedir.
Her denetleyici, aşağıdaki [InterfaceController] arayüzünü uygular:
<?php
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
interface InterfaceController {
// $config, uygulamanın 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 tablo döndürür: [$statusCode, $état, $content, $headers]
public function execute(
array $config,
Request $request,
Session $session,
array $infos=NULL): array;
}
Açıklamalar
- Tüm ikincil denetleyiciler, 17. satırdaki [execute] yöntemi aracılığıyla çalıştırılır. Bu yönteme, ana denetleyiciden alınan bilgiler aktarılır:
- 18. satır: Uygulamanın yapılandırmasını kapsayan [array $config];
- 19. satır: İşlenmekte olan HTTP isteği olan [Request $request];
- 20. satır: [Session $session]; bu, web uygulamasının mevcut oturumudur;
- 21. satır: [array $infos=NULL]; bu, yöntemin ilk üç parametresinin yetersiz kalması durumunda denetleyiciye yönelik ek bir bilgi dizisidir. Bu uygulamada bu parametre hiç kullanılmamıştır. İhtiyat amacıyla eklenmiştir;
- 21. satır: [execute] yöntemi, [$statusCode, $état, $content, $headers] dizisini döndürür
- [int $statusCode]: HTTP yanıtının durum kodu;
- [int $état]: Uygulamanın yürütme sonunda bulunduğu durum;
- [array $content]: [réponse=>résultat] adlı bir ilişkisel dizi; burada [résultat] herhangi bir türde olabilir: bu, denetleyici tarafından üretilen ve bir karakter dizisi biçiminde serileştirildikten sonra istemciye gönderilecek olan sonuçtur;
- [array $headers]: sunucunun HTTP yanıtına eklenecek HTTP başlıklarının listesi;
Her ikincil denetleyici, ana denetleyicinin aşağıdaki kodu tarafından çağrılır:
// kontrolörü kullanılarak eylem gerçekleştirilir
$controller = __NAMESPACE__ . $config["actions"][$action];
list($statusCode, $état, $content, $headers) = (new $controller())->execute($config, $request, $session);
- satırda, [execute] yönteminin 4. parametresi olan [array $infos=NULL]'in kullanılmadığı görülmektedir.
23.11. Eylemler
Şimdi web hizmetinin olası farklı eylemlerini gözden geçirelim:
Eylem | Rol | Çalıştırma bağlamı |
init-session | İstenen yanıtların türünü (json, xml, html) belirlemek için kullanılır | GET main.php?action=init-session&type=x her an gönderilebilir |
kullanıcı-kimlik-doğrulama | Bir kullanıcının oturum açmasına izin verir veya vermez | POST main.php?action=kullanıcı-doğrulama isteği İsteğin iki POST parametresi içermesi gerekir [user, password] Yalnızca oturum türü (json, xml, html) biliniyorsa gönderilebilir |
vergi-hesapla | Vergi hesaplaması simülasyonu yapar | İstek: POST main.php?action=calculer-impot İstek, üç POST parametresine sahip olmalıdır: [marié, enfants, salaire] Yalnızca oturum türü (json, xml, html) biliniyorsa ve kullanıcı kimliği doğrulanmışsa gönderilebilir |
simülasyonları-listele | Oturumun başlangıcından itibaren gerçekleştirilen simülasyonların listesini görüntüleme isteği | İstek: GET main.php?action=lister-simulations İstek başka hiçbir parametre kabul etmez Yalnızca oturum türü (json, xml, html) biliniyor ve kullanıcı kimliği doğrulanmışsa gönderilebilir |
simülasyonu-sil | Simülasyon listesinden bir simülasyonu siler | GET main.php?action=lister-simulations&numéro=x isteği İstek başka hiçbir parametre kabul etmez Yalnızca oturum türü (json, xml, html) biliniyor ve kullanıcı kimliği doğrulanmışsa gönderilebilir |
oturumu-sonlandır | Simülasyon oturumunu sonlandırır. | Teknik olarak eski web oturumu silinir ve yeni bir oturum oluşturulur Yalnızca oturum türü (json, xml, html) biliniyor ve kullanıcı kimliği doğrulanmışsa gönderilebilir |
Tüm ikincil denetleyiciler aynı şekilde hareket eder:
- parametrelerini kontrol ederler. Bu parametreler, URL'te bulunan parametreler için [Request→query] nesnesinde, gönderilen parametreler için ise (POST isteği) [Request→request] nesnesinde bulunur;
- Bir denetleyici, parametrelerinin geçerliliğini kontrol eden bir işlev veya yönteme benzer. Ancak denetleyici için durum biraz daha karmaşıktır:
- beklenen parametreler eksik olabilir;
- beklenen parametrelerin tümü karakter dizileridir, oysa bir fonksiyon parametrelerinin türünü belirleyebilir. Beklenen parametre bir sayı ise, parametre dizisinin gerçekten bir sayıya ait olup olmadığı kontrol edilmelidir;
- beklenen parametrelerin mevcut olduğu ve sözdizimsel olarak doğru olduğu doğrulandıktan sonra, bunların o anki yürütme bağlamında geçerli olup olmadığının kontrol edilmesi gerekir. Bu bağlam oturumda bulunur. Kimlik doğrulama örneği, bir yürütme bağlamına örnektir. Bazı eylemler, ancak müşteri kimliği doğrulandıktan sonra işlenmelidir. Genellikle, oturumdaki bir anahtar, bu kimlik doğrulamanın gerçekleşip gerçekleşmediğini gösterir;
- önceki kontroller tamamlandıktan sonra, ikincil denetleyici çalışmaya başlayabilir. Parametrelerin bu şekilde kontrol edilmesi çok önemlidir. Bir müşterinin, uygulamanın yaşam döngüsünün herhangi bir anında bize rastgele bir şey göndermesine izin verilemez. Uygulamanın yaşam döngüsünü tamamen kontrol etmeliyiz;
- işini tamamladıktan sonra, ikincil denetleyici, kendisini çağıran ana denetleyicinin beklediği [$statusCode, $état, $content, $headers] tablosunu döndürür;
Şimdi, web uygulamasının işleyişini yönlendiren farklı denetleyicileri, ya da başka bir deyişle farklı eylemleri gözden geçireceğiz.
23.11.1. [init-session] eylemi
[init-session] eylemi, aşağıdaki [InitSessionController] denetleyicisi tarafından işlenir:
<?php
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
class InitSessionController implements InterfaceController {
// $config, uygulamanın 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 {
// bir GET ve [action] dışında tek bir parametre bulunmalıdır
$method = strtolower($request->getMethod());
$erreur = $method !== "get" || $request->query->count() != 2;
if ($erreur) {
$état = 701;
$message = "méthode GET exigée avec paramètres [action, type] dans l'URL";
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// GET parametresini alıyoruz
$erreur = FALSE;
// tür
if (!$request->query->has("type")) {
$erreur = TRUE;
$état = 702;
$message = "paramètre [type] manquant";
} else {
$type = strtolower($request->query->get("type"));
}
// tür doğrulaması
if (!$erreur && !array_key_exists($type, $config["types"])) {
$erreur = TRUE;
$état = 703;
$message = "paramètre type [$type] invalide";
}
// hata mı?
if ($erreur) {
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// oturum türünü oturuma ekleniyor
$session->set("type", $type);
// başarı mesajı
$message = "session démarrée avec type [$type]";
$état = 700;
return [Response::HTTP_OK, $état, ["réponse" => $message], []];
}
}
Açıklamalar
- [GET main.php?action=init-session&type=xxx] isteği bekleniyor
- 25-26. satırlar: İsteğin, URL içinde iki parametreye sahip bir GET isteği olup olmadığı kontrol edilir;
- 27-31. satırlar: Aksi takdirde, hata kaydedilir ve ana denetleyiciye bir [$statusCode, $état, $content, $headers] sonucu gönderilir;
- satır 35-39: [type] parametresinin URL'te mevcut olup olmadığı kontrol edilir. Değilse, hata kaydedilir;
- 40. satır: Oturum türü kaydedilir;
- satır 43-47: oturum türünün (json, xml, html) terimlerinden biri olup olmadığı kontrol edilir. Aksi takdirde hata kaydedilir;
- 49-51. satırlar: Hata varsa, ana denetleyiciye [$statusCode, $état, $content, $headers] sonucunu gönderilir;
- 53. satır: oturum türü, web uygulamasının oturumuna kaydedilir;
- 55-57. satırlar: Denetleyici işini tamamladı. Ana denetleyiciye [$statusCode, $état, $content, $headers] başarı sonucu gönderilir;
Ana denetleyicinin, alt denetleyicilerin yanıtlarına ne yaptığını hatırlayalım:
// hatalar mı?
if ($erreurs) {
// yanıt gönderilmeden hazırlanıyor
$statusCode = Response::HTTP_BAD_REQUEST;
$content = ["réponse" => $erreurs];
$headers = [];
} else {
// ---------------------------
// kontrolör yardımıyla eylem gerçekleştiriliyor
$controller = __NAMESPACE__ . $config["actions"][$action];
$logger->write("contrôleur : $controller\n");
list($statusCode, $état, $content, $headers) = (new $controller())->execute($config, $request, $session);
}
// --------------------- yanıt gönderiliyor
// ölümcül hata durumu HTTP_INTERNAL_SERVER_ERROR
// mümkünse yöneticiye bir e-posta gönderilir
if ($statusCode === Response::HTTP_INTERNAL_SERVER_ERROR && $config['adminMail'] != NULL) {
$infosMail = $config['adminMail'];
$infosMail['message'] = json_encode($content, JSON_UNESCAPED_UNICODE);
$sendAdminMail = new SendAdminMail($infosMail, $logger);
$sendAdminMail->send();
}
// yanıt, oturum türüne bağlıdır
if ($session->has("type")) {
// oturum türü oturumda bulunur
$type = $session->get("type");
} else {
// oturumda tür yoksa, varsayılan olarak jSON biçiminde bir yanıt verilir
$type = "json";
}
// denetleyicinin yanıtına [action, état] anahtarları eklenir
$content = ["action" => $action, "état" => $état] + $content;
// Yanıtı istemciye göndermekle görevli [Response] nesnesi oluşturulur
$response = __NAMESPACE__ . $config["types"][$type]["response"];
(new $response())->send($request, $session, $config, $statusCode, $content, $headers, $logger);
// yanıt gönderildi - kaynaklar serbest bırakılır
$logger->close();
exit;
- 12. satır: Ana denetleyici, ikincil denetleyicinin sonucunu alır;
- 35-36. satırlar: Bazı kontrollerden sonra, mevcut oturumun türüne (json, xml, html) göre [JsonResponse, XmlResponse, HtmlResponse] sınıflarından birini oluşturarak yanıtı gönderir;
Bundan sonra, [json] türündeki bir simülasyon oturumu kapsamında [Postman] testleri yapacağız. [JsonResponse] sınıfının işleyişi, bağlantı paragrafında açıklanmıştır.
23.11.2. [Postman] Testleri

Yukarıda:
- [2]'te üç yeni test;
- [3-7]'te, [type] parametresi eksik olan [init-session] eylemi;
- [8-11]'te, sunucudan gelen jSON yanıtı;

Yukarıda:
- [1-7]'te, [init-session] eylemi, [type] parametresinin hatalı olması nedeniyle;
- [8-11]'te, sunucudan gelen jSON yanıtı;

Yukarıda:
- [1-8]'te, [init-session] eylemi, jSON türüyle;
- [9-12]'te, sunucudan gelen jSON yanıtı;
23.11.3. [authentifier-utilisateur] eylemi
[authentifier-utilisateur] eylemi, aşağıdaki [AuthentifierUtilisateurController] denetleyicisi tarafından yürütülü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;
class AuthentifierUtilisateurController implements InterfaceController {
// $config, uygulamanın 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 {
// bir POST ve tek bir GET parametresi olmalıdır
$method = strtolower($request->getMethod());
$erreur = $method !== "post" || $request->query->count() != 1;
if ($erreur) {
$état = 201;
$message = "méthode POST requise, paramètre [action] dans l'URL, paramètres postés [user,password]";
// sonuç ana denetleyiciye gönderilir
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// POST parametrelerini alıyoruz
$erreurs = [];
// kullanıcı
$état = 210;
if (!$request->request->has("user")) {
$état += 2;
$erreurs[] = "paramètre [user] manquant";
} else {
$user = $request->request->get("user");
}
// şifre
if (!$request->request->has("password")) {
$état += 4;
$erreurs[] = "paramètre [password] manquant";
} else {
$password = trim($request->request->get("password"));
}
// hata mı?
if ($erreurs) {
// sonuç ana denetleyiciye gönderilir
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $erreurs], []];
}
// kullanıcı kimlik bilgilerinin doğrulanması
// kullanıcı mevcut mu?
$users = $config["users"];
$i = 0;
$trouvé = FALSE;
while (!$trouvé && $i < count($users)) {
$trouvé = ($user === $users[$i]["login"] && $users[$i]["passwd"] === $password);
$i++;
}
// bulundu mu?
if (!$trouvé) {
// hata mesajı
$message = "Echec de l'authentification [$user, $password]";
$état = 221;
// sonuç ana denetleyiciye gönderilir
return [Response::HTTP_UNAUTHORIZED, $état, ["réponse" => $message], []];
} else {
// kullanıcının kimliği doğrulandığını oturuma kaydediliyor
$session->set("user", TRUE);
// başarı mesajı
$message = "Authentification réussie [$user, $password]";
$état = 200;
// sonuç ana denetleyiciye gönderilir
return [Response::HTTP_OK, $état, ["réponse" => $message], []];
}
}
}
Açıklamalar
- [POST main.php?action=authentifier-utilisateur] isteği, [user, password] ile gönderilen iki parametre beklenir;
- 24-25. satırlar: URL içinde tek bir parametre içeren bir POST isteği olup olmadığı kontrol edilir;
- satır 26-31: Hata varsa, bu hata kaydedilir ve ana denetleyiciye [$statusCode, $état, $content, $headers] sonucu döndürülür;
- satır 36-39: gönderilen değerlerde [user] parametresinin varlığı kontrol edilir. Eğer yoksa, hata kaydedilir;
- satır 43-45: gönderilen değerlerde [password] parametresinin varlığı kontrol edilir. Eğer yoksa, hata kaydedilir;
- satır 50-53: Kaydedilen değerlerden biri eksikse, ana denetleyiciye bir [$statusCode, $état, $content, $headers] sonucu gönderilir;
- satır 56-62: Alınan [$user,$password] çifti, yapılandırma dosyasındaki [$config[‘users’]] tablosunda mevcut mu kontrol edilir;
- 64-69. satırlar: Eğer mevcut değilse, hata kaydedilir. HTTP durum kodu, [Response::HTTP_UNAUTHORIZED] olarak güncellenir ve [$statusCode, $état, $content, $headers] sonucu ana denetleyiciye iletilir;
- 72. satır: Kimlik doğrulama başarılı oldu. Bu durum, oturuma [user] anahtarı eklenerek kaydedilir. Kimlik doğrulamanın başarılı olduğunu gösteren, bu anahtarın varlığıdır;
- 73-77. satırlar: Baş denetleyiciye [$statusCode, $état, $content, $headers] başarı sonucu gönderilir;
23.11.4. [Postman] Testleri
[AuthentifierUtilisateurController] denetleyicisinin [Postman] testlerini jSON modunda gerçekleştiriyoruz;

Yukarıda:
- [1-6]'te, GET [2] ile [authentifier-utilisateur] eylemi gerçekleştiriliyor, oysa POST gerekli;
- [7-10]'te, sunucudan gelen jSON yanıtı;
GET'i, yanıt gövdesine herhangi bir parametre eklemeden POST ve [2] ile değiştirelim:

Yukarıda:
- [1-7]'te, POST'e gönderilen parametreler olmadan [7];
- [8-11]'te, sunucudan gelen jSON yanıtı;
Şimdi, isteğin [4] gövdesine (body) bir [password] parametresi ekleyelim:

Yukarıda:
- [1-6]'te, POST [2] isteği, [password] parametresiyle birlikte [4-6] olarak gönderilmiştir. Gönderilen parametreler, [4] isteğinin gövdesine (body) eklenmelidir. Sunucuya değerleri göndermenin birkaç yolu vardır. Biz [x-www-form-urlencoded] ve [5] yöntemlerini seçiyoruz;
- [8-10]'te, sunucudan gelen jSON yanıtı;
Şimdi [password] parametresi olmadan [user] parametresini tanımlayalım:

Yukarıda:
- [1-7]'te, [password] [4-7] parametresi olmadan POST isteği;
- [8-11]'te, sunucudan gelen jSON yanıtı;
Şimdi, [user, password] adlı iki parametreyi, kimlik doğrulamanın başarısız olmasına neden olacak değerlerle gönderelim:

Yukarıda:
- [1-9]'te, hatalı [user, password] POST parametrelerine sahip bir POST isteği;
- [10-13]'te, sunucudan gelen jSON yanıtı. Yanıtın durum kodu [401 Unauthorized] [10]'e dikkat edin;
Şimdi geçerli kimlik bilgileriyle bir POST isteği:

Yukarıda:
- [1-9]'te, geçerli kimlik bilgilerine sahip POST, [2] ve [6-9] istekleri;
- [10-13]'te, sunucudan gelen jSON yanıtı. HTTP, [200 OK] ve [10] durum kodlarına dikkat edilmelidir;
23.11.5. [calculer-impot] eylemi
[calculer-impot] eylemi, aşağıdaki [CalculerImpotController] denetleyicisi tarafından işlenir:
<?php
namespace Application;
// Symfony bağımlılıkları
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
// [dao] katmanının takma adı
use \Application\ServerDaoWithSession as ServerDaoWithRedis;
class CalculerImpotController 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
// [$statusCode, $état, $content, $headers] dizisini döndürür
public function execute(
array $config,
Request $request,
Session $session,
array $infos = NULL): array {
// bir GET parametresi ve üç POST parametresi bulunmalıdır
$method = strtolower($request->getMethod());
$erreur = $method !== "post" || $request->query->count() != 1;
if ($erreur) {
// hata kaydedilir
$message = "il faut utiliser la méthode [post] avec [action] dans l'URL et les paramètres postés [marié, enfants, salaire]";
$état = 301;
// sonuç ana denetleyiciye geri gönderilir
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// POST parametreleri alınır
$erreurs = [];
$état = 310;
// medeni durum
if (!$request->request->has("marié")) {
$état += 2;
$erreurs[] = "paramètre [marié] manquant";
} else {
$marié = trim(strtolower($request->request->get("marié")));
$erreur = $marié !== "oui" && $marié !== "non";
if ($erreur) {
$état += 4;
$erreurs[] = "valeur [$marié] invalide pour le paramètre [marié]";
}
}
// çocuk sayısı alınır
if (!$request->request->has("enfants")) {
$état += 8;
$erreurs[] = "paramètre [enfants] manquant";
} else {
$enfants = trim($request->request->get("enfants"));
$erreur = !preg_match("/^\d+$/", $enfants);
if ($erreur) {
$état += 9;
$erreurs[] = "valeur [$enfants] invalide pour le paramètre [enfants]";
}
}
// yıllık maaş alınır
if (!$request->request->has("salaire")) {
$erreurs[] = "paramètre [salaire] manquant";
$état += 16;
} else {
$salaire = trim($request->request->get("salaire"));
$erreur = !preg_match("/^\d+$/", $salaire);
if ($erreur) {
$état += 17;
$erreurs[] = "valeur [$salaire] invalide pour le paramètre [salaire]";
}
}
// hata mı?
if ($erreurs) {
// sonuç ana denetleyiciye geri gönderiliyor
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $erreurs], []];
}
// Çalışmak için gerekli her şeye sahibiz
// Redis
\Predis\Autoloader::register();
try {
// [predis] istemcisi
$redis = new \Predis\Client();
// sunucuya bağlanıp orada olup olmadığını kontrol ediyoruz
$redis->connect();
} catch (\Predis\Connection\ConnectionException $ex) {
// işler ters gitti
// hata içeren sonuç ana denetleyiciye geri gönderilir
$état = 350;
return [Response::HTTP_INTERNAL_SERVER_ERROR, $état,
["réponse" => "[redis], " . utf8_encode($ex->getMessage())], []];
}
// geçerli parametreler var
// [dao] katmanının oluşturulması
if (!$redis->get("taxAdminData")) {
try {
// veritabanından vergi verilerini alıyoruz
$dao = new ServerDaoWithRedis($config["databaseFilename"], NULL);
// Alınan veriler Redis'e kaydediliyor
$redis->set("taxAdminData", $dao->getTaxAdminData());
} catch (\RuntimeException $ex) {
// işlem başarısız oldu
// hata içeren sonuç ana denetleyiciye geri gönderiliyor
$état = 340;
return [Response::HTTP_INTERNAL_SERVER_ERROR, $état,
["réponse" => utf8_encode($ex->getMessage())], []];
}
} else {
// Vergi verileri [application] kapsam belleğinden alınır
$arrayOfAttributes = \json_decode($redis->get("taxAdminData"), true);
$taxAdminData = (new TaxAdminData())->setFromArrayOfAttributes($arrayOfAttributes);
// [dao] katmanının örneklenmesi
$dao = new ServerDaoWithRedis(NULL, $taxAdminData);
}
// [métier] katmanının oluşturulması
$métier = new ServerMetier($dao);
// Çalışmak için gereken her şey hazır - vergi hesaplaması
$résultat = $métier->calculerImpot($marié, (int) $enfants, (int) $salaire);
// az önce yapılan simülasyonu oturuma ekliyoruz
$simulation = new Simulation();
$résultat = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $résultat;
$simulation->setFromArrayOfAttributes($résultat);
// Oturumda bir simülasyon listesi var mı?
if (!$session->has("simulations")) {
$simulations = [];
} else {
$simulations = $session->get("simulations");
}
// simülasyon simülasyon listesine ekleniyor
$simulations[] = $simulation;
// simülasyonları oturuma geri yüklenir
$session->set("simulations", $simulations);
// sonuç ana denetleyiciye geri gönderilir
$état = 300;
return [Response::HTTP_OK, $état, ["réponse" => $résultat], []];
}
}
Açıklamalar
- Beklenen istek, üç parametre gönderilen [POST main.php?action=calculer-impot]'tir. [marié, enfants, salaire]:
- [marié]'in değeri, [oui, non]'te bulunmalıdır;
- [enfants, salaire], pozitif tamsayılar veya sıfır olmalıdır;
- 26-27. satırlar: URL içinde tek bir parametreye sahip bir POST olup olmadığı kontrol edilir;
- satır 28-34: Aksi takdirde, ana denetleyiciye bir hata sonucu gönderilir;
- satır 36: hata mesajları [$erreurs] tablosunda biriktirilir;
- satır 39-41: [marié] parametresinin varlığı kontrol edilir. Eğer yoksa, hata kaydedilir;
- satır 43-49: [marié] parametresinin değerinin [oui, non]'te olup olmadığı kontrol edilir. Eğer yoksa, hata kaydedilir;
- satır 51-54: [enfants] parametresinin varlığı kontrol edilir. Eğer yoksa, hata kaydedilir;
- satır 55-61: [enfants] parametresinin değerinin pozitif bir sayı veya sıfır olup olmadığı kontrol edilir. Aksi takdirde hata kaydedilir;
- satır 63-66: [salaire] parametresinin mevcut olup olmadığı kontrol edilir. Mevcut değilse, hata kaydedilir;
- satır 67-72: [salaire] parametresinin değerinin pozitif bir sayı veya sıfır olup olmadığı kontrol edilir. Aksi takdirde hata kaydedilir;
- 75-78. satırlar: [$erreurs] dizisi boş değilse, hatalar olduğu anlamına gelir. Hata dizisi cevaba eklenir ve sonuç ana denetleyiciye gönderilir;
- satır 80: geçerli parametreler mevcuttur. Vergi hesaplanabilir. Bunun için, bu hesaplamayı yapabilen [dao] ve [métier] katmanları oluşturulmalıdır;
- satır 82-94: [Redis] adlı bir istemci oluşturulur;
- 88-94. satırlar: [Redis] sunucusuna bağlanılamadıysa, müşteriye bir [500 Internal Server Error] kodu gönderilir;
- 98. satır: [Redis] sunucusunda [taxAdminData] anahtarı olup olmadığına bakılır. Bu anahtar, vergi idaresine ait verileri temsil eder. Anahtar mevcut değilse, vergi verileri veritabanından aranmalıdır;
- satır 101: Vergi verilerinin veritabanından alınması gerektiğinde [dao] katmanının oluşturulması. [ServerDaoWithRedis] sınıfı, “bağlantı” başlıklı paragrafta açıklanmıştır;
- satır 103: Veritabanından alınan veriler, [taxAdminData] anahtarıyla [Redis] belleğine kaydedilir;
- satır 104-110: Veritabanı araması başarısız olursa, [dao] katmanı tarafından döndürülen hata kaydedilir ve ana denetleyiciye gönderilen sonuca eklenir;
- satır 109: [PDO] katmanı tarafından döndürülen hata mesajı, [iso-8859-1] olarak kodlanmıştır. Bu mesaj, [utf-8] olarak kodlanır;
- satır 111-117: [taxAdminData] anahtarı [Redis] belleğinde mevcutsa, vergi verileri doğrudan [dao] katman oluşturucusuna aktarılır;
- satır 119: [métier] katmanı oluşturulur. [ServerMetier] sınıfı, “bağlantı” başlıklı paragrafta açıklanmıştır;
- 124-126. satırlar: Hesaplanan vergi tutarı ile birlikte bir [Simulation] nesnesi oluşturulur. [Simulation] sınıfı, bir simülasyonun verilerini kapsar ve “bağlantı” başlıklı paragrafta açıklanmıştır;
- 128-132. satırlar: Az önce oluşturulan simülasyon, daha önce hesaplanmış simülasyonlar listesine eklenmelidir. Henüz hiçbir simülasyon yapılmamışsa hariç, bu liste oturumda bulunur;
- satır 133-136: Simülasyon, simülasyonlar listesine eklenir ve liste oturuma geri aktarılır;
- satır 137-139: sonuç ana denetleyiciye gönderilir;
23.11.6. [Postman] Testleri
[Postman] denemelerini, [CalculerImpotController] denetleyicisi üzerinde jSON modunda gerçekleştiriyoruz;

Yukarıda:
- [1-7]'te, [POST] yerine [GET] isteği yapılıyor;
- [8-11]'te, sunucudan gelen yanıt jSON;
Şimdi, [POST] yöntemini, POST parametreleri olsun ya da olmasın ve geçersiz POST parametreleriyle birlikte kullanalım:

Yukarıda:
- [POST] [2] isteği, geçersiz [6-11] [marié, enfants, salaire] POST parametreleriyle gönderiliyor. [16]'te ilgili kutucuğun işaretini kaldırarak bu parametrelerden birini göndermeyebilirsiniz. Bu, farklı senaryoları test etmenizi sağlayacaktır. Yukarıdaki ekran görüntüsünde, üç parametre de mevcut ve hepsi geçersiz;
- [12-15]'te, sunucudan gelen yanıt jSON;
Şimdi gönderilen üç parametreden ikisinin işaretini kaldıralım:

Yukarıda,
- [5-8]'te, yalnızca [salaire] parametresi gönderilmiştir ve üstelik bu parametre geçersizdir;
- [9-11]'te, sunucudan gelen sonuç jSON;
Şimdi geçerli parametrelerle bir vergi hesaplaması yapalım:

Yukarıda:
- [1118]'te, geçerli parametreler içeren bir istek: [6-8];
- [12-14]'te, sunucudan gelen jSON yanıtı;
23.11.7. [lister-simulations] eylemi
[lister-simulations] eylemi, aşağıdaki [ListerSimulationsController] ikincil denetleyicisi tarafından işlenir:
<?php
namespace Application;
// Symfony bağımlılıkları
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
class ListerSimulationsController {
// $config, uygulamanın 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 parametreye sahip olmalıdır GET
$method = strtolower($request->getMethod());
$erreur = $method !== "get" || $request->query->count() != 1;
if ($erreur) {
$état = 501;
$message = "GET requis, avec l'unique paramètre [action] dans l'URL";
// ana denetleyiciye hatalı bir sonuç döndürülür
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// oturumdaki simülasyon listesi alınır
if (!$session->has("simulations")) {
$simulations = [];
} else {
$simulations = $session->get("simulations");
}
// ana denetleyiciye başarılı bir sonuç gönderilir
$état = 500;
return [Response::HTTP_OK, $état, ["réponse" => $simulations], []];
}
}
Yorumlar
- [GET main.php?action=lister-simulations] isteği;
- 24-25. satırlar: Tek bir parametreye sahip bir GET isteği olup olmadığı kontrol edilir;
- 26-31. satırlar: durum böyle değilse, ana denetleyiciye hata içeren bir sonuç döndürülür;
- satır 33-37: oturumda varsa simülasyon listesi alınır (satır 36), yoksa bu liste boştur (satır 34);
- satır 39-40: simülasyon listesi ana denetleyiciye gönderilir;
23.11.8. [Postman] Testleri
Bir hata testi ve bir başarılı test olmak üzere iki test oluşturacağız.

Yukarıda:
- [1-8]'te, [GET] sorgusu, URL [3, 7-8]'te fazladan bir [param1] parametresiyle gerçekleştirilir;
- [9-12]'te, sunucudan gelen jSON yanıtı;
Şimdi geçerli bir istek yapalım:

Yukarıda:
- [1-5], geçerli bir istek;
İsteğin sonucu şöyledir:

- [3-6]'te, sunucudan gelen jSON yanıtı. Bu testten önce, sunucunun web oturumunda simülasyonlar oluşturmak amacıyla [Postman] ve [calculer-impot-300] testleri birkaç kez gerçekleştirilmişti;
23.11.9. [supprimer-simulation] eylemi
[supprimer-simulation] eylemi, aşağıdaki ikincil denetleyici [SupprimerSessionController] tarafından işlenir:
<?php
namespace Application;
// Symfony bağımlılıkları
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
class SupprimerSimulationController {
/// $config, uygulamanın 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 {
// iki parametre bulunmalıdır: GET
$method = strtolower($request->getMethod());
$erreur = $method !== "get" || $request->query->count() != 2;
$état = 600;
if ($erreur) {
$état += 2;
$message = "GET requis, avec les paramètres [action, numéro]";
}
// [numéro] parametresi mevcut olmalıdır
if (!$erreur) {
$état += 4;
$erreur = !$request->query->has("numéro");
if ($erreur) {
$message = "paramètre [numéro] manquant";
}
}
// [numéro] parametresi geçerli olmalıdır
if (!$erreur) {
$état += 8;
$numéro = $request->query->get("numéro");
$erreur = !preg_match("/^\d+$/", $numéro);
if ($erreur) {
$message = "paramètre [$numéro] invalide";
}
}
// [numéro] parametresi [0,n-1] aralığında olmalıdır
// n simülasyon sayısı ise
if (!$erreur) {
$numéro = (int) $numéro;
$erreur = !$session->has("simulations");
if (!$erreur) {
$simulations = $session->get("simulations");
$erreur = $numéro < 0 || $numéro >= count($simulations);
}
if ($erreur) {
$état += 16;
$message = "la simulation n° [$numéro] n'existe pas";
}
}
// hata mı?
if ($erreur) {
// sonuç ana denetleyiciye gönderilir
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// simülasyon siliniyor $numéro
unset($simulations[$numéro]);
$simulations = array_values($simulations);
// simülasyonlar oturuma geri ekleniyor
$session->set("simulations", $simulations);
// simülasyon listesi müşteriye gönderiliyor
$état = 600;
return [Response::HTTP_OK, $état, ["réponse" => $simulations], []];
}
}
Yorumlar
- [GET main.php?action=supprimer-simulation&numéro=x] isteği;
- 24-30. satırlar: iki parametreli bir GET isteği olup olmadığı kontrol edilir;
- satır 32-38: [numéro] parametresinin URL parametreleri arasında mevcut olup olmadığı kontrol edilir;
- satır 40-47: [numéro] parametresinin değerinin sözdizimsel olarak doğru olup olmadığı kontrol edilir;
- satır 50-61: [numéro] numaralı simülasyonun gerçekten mevcut olup olmadığı kontrol edilir. İki hata durumu söz konusudur:
- simülasyon listesi oturumda bulunamıyor (satır 52);
- silinecek simülasyonun [numéro] numarası simülasyon listesinde mevcut değildir;
- satır 63-66: hata durumunda, ana denetleyiciye hata içeren bir sonuç gönderilir;
- 68. satır: [numéro] numaralı simülasyon silinir;
- satır 69: [unset] işlemi, listedeki [0, n-1] indekslerini değiştirmez. Bunları güncellemek için, eksik simülasyonu ortadan kaldırmak üzere [$simulations] tablosundaki değerler istenir;
- satır 71: yeni simülasyon tablosu oturuma geri yüklenir;
- 73-74. satırlar: yeni simülasyon listesi ana denetleyiciye geri gönderilir;
23.11.10. [Postman] Testleri
Hata ve başarı testleri yapacağız:

Yukarıda:
- [1-6]'te, [numéro] parametresi olmadan GET isteği;
- [7-10]'te, sunucudan gelen jSON yanıtı;
Şimdi de sözdizimi açısından hatalı bir numaraya sahip bir istek:

Yukarıda:
- [1-5]'te, geçersiz [numéro] parametresine sahip GET isteği [3, 5];
- [6-9]'te, sunucudan gelen jSON yanıtı;
Şimdi, mevcut olmayan bir simülasyon numarası içeren bir istek:

Yukarıda:
- [1-5]'te, simülasyon listesinde bulunmayan 100 numaralı simülasyon numarasına sahip bir istek;
- [6-9]'te, sunucudan gelen jSON yanıtı;
Şimdi, listeden 0 numaralı simülasyonu, yani ilk simülasyonu sileceğiz. Öncelikle, [lister-simulations-500] isteği ile bu listeyi tekrar isteyelim:

- [1]'te şu anda 2 simülasyon bulunmaktadır;
İlk simülasyonu (numara 0) siliyoruz:

Yukarıda:
- [1-5]'te, 0 numaralı simülasyon olan [5] siliniyor;
- [6-9]'te, sunucudan gelen jSON yanıtı. 0 numaralı simülasyonun silindiği görülüyor;
Bu işlemi tekrarlayalım:

Yukarıda:
- [1]'te, sunucunun web oturumunda artık simülasyon kalmamıştır;
23.11.11. [fin-session] eylemi
[fin-session] eylemi, aşağıdaki ikincil denetleyici [FinSessionController] tarafından işlenir:
<?php
namespace Application;
// Symfony bağımlılıkları
use \Symfony\Component\HttpFoundation\Response;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
class FinSessionController implements InterfaceController {
// $config, uygulamanın 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;
// hata mı?
if ($erreur) {
$état = 401;
// ana denetleyiciye gönderilen sonuç
$message = "GET requis avec le seul paramètre [action] dans l'URL";
return [Response::HTTP_BAD_REQUEST, $état, ["réponse" => $message], []];
}
// oturum türü kaydediliyor
$type = $session->get("type");
// mevcut oturum geçersiz kılınıyor
$session->invalidate();
// yeni oturuma tür yeniden atanıyor
$session->set("type", $type);
// yanıtın gönderilmesi
$état = 400;
// sonuç ana denetleyiciye gönderilir
$content = ["réponse" => "session supprimée"];
return [Response::HTTP_OK, $état, $content, []];
}
}
Yorumlar
- [GET main.php?action=fin-session] isteği;
- 25-33. satırlar: eylemin, tek parametresi [fin-action] olan bir GET olup olmadığı kontrol edilir;
- 38. satır: mevcut oturum geçersiz kılınır. Bu, oturumda kaydedilen verileri siler ve yeni bir oturum başlatılır;
- satır 36: oturum sona ermeden önce, oturumun [json, xml, html] türü kaydedilir;
- satır 40: önceki oturumun türü yeni oturuma aktarılır. Son olarak, [type] benzersiz anahtarına sahip yeni bir oturumla devam edilir;
- 44-45. satırlar: sonuç ana denetleyiciye gönderilir;
23.11.12. [Postman] Testleri
Bir hata testi ve bir başarı testi yapacağız:

Yukarıda:
- [1-5]'te, beklenen GET yerine POST [2] ile [5] oturumunun sonlandırılması isteniyor;
- [6-9]'te, sunucudan gelen yanıt jSON;
Şimdi de başarılı bir örneğe bakalım. Öncelikle, son test sırasında istemci [Postman] ile sunucu arasında alışverişi yapılan oturum çerezine bir göz atalım:

Yukarıda:
- [3], istemci [Postman] tarafından sunucuya gönderilen oturum çerezi;
Şimdi de sunucunun yanıtında gönderdiği HTTP başlıklarına bakalım:

Yukarıda:
- [3-4]'te, oturum çerezi sunucunun yanıtında yer almıyor. Bu normaldir. Sunucu bu çerezi yalnızca bir kez, yeni bir web oturumunun başlangıcında gönderir;
Şimdi geçerli bir [fin-session] eylemini gerçekleştirelim:

Yukarıda:
- [1-3]'te, geçerli bir [fin-session] eylemi;
- [4-7]'te, sunucudan gelen jSON yanıtı;
Sunucunun yanıtında gönderilen HTTP başlıklarına bir göz atalım:

- [3]'te, sunucu [Set-Cookie] başlığını gönderir ve böylece yeni bir web oturumunun başladığını gösterir;
23.12. Sunucunun yanıt türleri
23.12.1. Giriş
Uygulamanın genel mimarisine tekrar göz atalım:

Olası yanıt türlerini [3a] şeklinde sunacağız. Bunlar, projenin [Responses] klasöründe toplanmıştır:

[JsonResponse] sınıfını daha önce bağlantı paragrafında tanıtmıştık. Bu sınıf, [InterfaceResponse] arayüzünü uygular ve [ParentResponse] sınıfını genişletir. Aynı durum, diğer iki sınıf olan [XmlResponse] ve [HtmlResponse] için de geçerlidir.
[InterfaceResponse] arayüzünün tanımını hatırlayalım:
<?php
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
interface InterfaceResponse {
// $request isteği: istek işleniyor
// $session oturumu: web uygulamasının oturumu
// $config dizisi: uygulama yapılandırması
// int statusCode : yanıt durum kodu HTTP
// dizi $content : sunucu yanıtı
// dizi $headers : yanıta eklenecek HTTP başlıkları
// Logger $logger: günlükleri yazmak için kullanılan günlük kaydedici
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void;
}
- 19-27. satırlar: [InterfaceResponse] arayüzü, yanıtı istemciye göndermek için tek bir [send] yöntemine sahiptir;
- satır 11-17: [send] yöntemindeki çeşitli parametrelerin anlamları;
- satır 23-25: [$statusCode, $content, $headers] parametreleri, uygulamanın ikincil denetleyicilerinin standart yanıtıdır. Ancak yanıtın başka bilgilere de ihtiyacı olabilir. Bu nedenle, isteğe, oturuma ve yapılandırmaya ilişkin tüm bilgilere erişimini sağlayan ilk üç parametre (satır 20-22) bu yönteme verilir;
- satır 26: Yanıt, müşteriye gönderilen yanıtı günlüğe kaydedeceği için [Logger]'e ihtiyaç duyar;
Şimdi, üç yanıt türünün ortak özelliğini (müşteriye bir metin yanıtının fiilen gönderilmesi) özetleyen üst sınıf olan [ParentResponse] sınıfının kodunu hatırlayalım:
<?php
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Response;
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(
int $statusCode,
string $content,
array $headers): void {
// sunucudan gelecek metin yanıtının hazırlanması
$response = new Response();
$response->setCharset("utf-8");
// durum kodu
$response->setStatusCode($statusCode);
// başlıklar
foreach ($headers as $text => $value) {
$response->headers->set($text, $value);
}
// yanıtın gönderilmesi
$response->setContent($content);
$response->send();
}
}
Açıklamalar
- 10-13. satırlar: [send] yönteminin üç parametresinin anlamı;
- 17. satır: Yanıt gövdesinin [string] türü olduğu ve dolayısıyla gönderilmeye hazır olduğu (30. satır) dikkat çekmektedir;
- 22. satır: Yanıtta UTF-8 karakterleri yer alacaktır;
- satır 24: yanıtın durum kodu HTTP;
- 26-28. satırlar: arayan kodu tarafından verilen HTTP başlıklarının eklenmesi;
- satır 30-31: yanıtın müşteriye gönderilmesi;
Son olarak, yanıtın müşteriye gönderilmesini talep eden ana denetleyicinin kodunu hatırlayalım:
// [action, état] anahtarları denetleyicinin yanıtına ekleniyor
$content = ["action" => $action, "état" => $état] + $content;
// yanıtı istemciye göndermekle görevli [Response] nesnesi oluşturulur
$response = __NAMESPACE__ . $config["types"][$type]["response"];
(new $response())->send($request, $session, $config, $statusCode, $content, $headers, $logger);
// yanıt gönderildi - kaynaklar serbest bırakılır
$logger->close();
exit;
- 4. satır: örneklenecek [Response] sınıfının adı belirlenir;
- satır 5: sınıfın bir örneği oluşturulur ve [send($request, $session, $config, $statusCode, $content, $headers, $logger)] yöntemi kullanılarak yanıt müşteriye gönderilir. Farklı yanıt türlerine ait [send] yöntemleri, aynı [InterfaceResponse] arayüzünü uyguladıkları için hepsi aynı imza yapısına sahiptir;
23.12.2. [JsonResponse] sınıfı
Bu sınıf, bağlantı paragrafında daha önce tanıtılmıştır. Ancak, üç yanıt sınıfının homojenliğini daha iyi vurgulamak için kodunu tekrar veriyoruz:
[JsonResponse] sınıfı, [InterfaceResponse] arayüzünü şu şekilde uygular:
<?php
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use \Symfony\Component\HttpFoundation\Request;
use \Symfony\Component\HttpFoundation\Session\Session;
class JsonResponse extends ParentResponse implements InterfaceResponse {
// $request isteği: istek işleniyor
// $session oturumu: web uygulamasının oturumu
// $config dizisi: uygulama yapılandırması
// int statusCode : yanıt durum kodu HTTP
// dizi $content : sunucunun yanıtı
// dizi $headers: yanıtta eklenecek HTTP başlıkları
// $logger günlüğü: günlükleri yazmak için kullanılan günlüğü
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void {
// Symfony serileştiricisinin hazırlanması
$serializer = new Serializer(
[
// nesnelerin serileştirilmesi için gereklidir
new ObjectNormalizer()],
// jSON kodlayıcı
// seçenekler için, farklı seçenekler arasına OU ekleyin
[new JsonEncoder(new JsonEncode([JsonEncode::OPTIONS => JSON_UNESCAPED_UNICODE]))]
);
// serileştirme jSON
$json = $serializer->serialize($content, 'json');
// başlıklar
$headers = array_merge($headers, ["content-type" => "application/json"]);
// yanıt gönderme
parent::sendResponse($statusCode, $json, $headers);
// günlük
if ($logger !== NULL) {
$logger->write("réponse=$json\n");
}
}
}
Açıklamalar
- 13. satır: Sınıf, [InterfaceResponse] arayüzünü uygular;
- 13. satır: Sınıf, [ParentResponse] sınıfını genişletir. Tüm [Response] türleri bu sınıfı genişletir. İstemciye yanıtı gönderen bu üst sınıftır (46. satır). Bu kod, tüm [Response] türleri için ortak olduğundan, bir üst sınıfa ayrıştırılmıştır;
- satır 33-40: [Symfony] serileştiricisinin örneklenmesi; bu serileştirici, [$content] sunucusunun yanıtını jSON dizesine dönüştürecektir (satır 42);
- satır 34-36: [Serializer] oluşturucusunun ilk parametresi bir dizidir. Bu diziye, nesnelerin serileştirilmesi için gerekli olan [ObjectNormalizer] sınıfının bir örneği eklenir. Bu durum, bu uygulamada her bir simülasyonun [Simulation] sınıfının bir örneği olduğu bir simülasyon listesiyle karşımıza çıkar;
- 39. satır: [Serializer] yapıcı işlevinin ikinci parametresi de bir dizidir: buraya bir serileştirmede kullanılan tüm kodlayıcılar eklenir (XML, jSON, CSV…);
- satır 39: burada sadece [JsonEncoder] türünde bir kodlayıcı olacaktır. Parametresiz oluşturucu yeterli olabilirdi. Burada, yalnızca jSON kodlama seçeneklerini aktarmak amacıyla oluşturucuya [JsonEncode] parametresini aktardık;
- satır 39: [JsonEncode] oluşturucu parametresi bir seçenekler dizisidir. Burada, [JSON_UNESCAPED_UNICODE] seçeneği kullanılarak, jSON dizesindeki UTF-8 karakterlerinin "kaçış karakterleri" kullanılmadan doğal olarak görüntülenmesi talep edilmektedir;
- 42. satır: HHTP yanıt gövdesi, önceki serileştirici sayesinde jSON olarak serileştirilir;
- 44. satır: İstemciye jSON gönderileceğini bildiren HTTP başlığı eklenir;
- 46. satır: üst sınıftan yanıtın müşteriye gönderilmesi istenir;
- 48-50. satırlar: jSON yanıtı günlüğe kaydedilir;
23.12.3. [XmlResponse] sınıfı
[XmlResponse] sınıfı, [InterfaceResponse] arayüzünü şu şekilde uygular:
<?php
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
class XmlResponse extends ParentResponse implements InterfaceResponse {
// $request isteği: istek işleniyor
// Oturum $session: web uygulamasının oturumu
// $config dizisi: uygulama yapılandırması
// int statusCode : yanıt durum kodu HTTP
// dizi $content : sunucu yanıtı
// dizi $headers : yanıta eklenecek HTTP başlıkları
// Logger $logger: günlükleri yazmak için kullanılan günlük kaydedici
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void {
// Symfony serileştiricisinin hazırlanması
$serializer = new Serializer(
// nesnelerin serileştirilmesi için gereklidir
[new ObjectNormalizer()],
[
// serileştirme XML
new XmlEncoder(
[
XmlEncoder::ROOT_NODE_NAME => 'root',
XmlEncoder::ENCODING => 'utf-8'
]
),
// serileştirme jSON
new JsonEncoder(new JsonEncode([JsonEncode::OPTIONS => JSON_UNESCAPED_UNICODE]))
]
);
// serileştirme XML
$xml = $serializer->serialize($content, 'xml');
// başlıklar
$headers = array_merge($headers, ["content-type" => "application/xml"]);
// yanıt gönderme
parent::sendResponse($statusCode, $xml, $headers);
// günlük
if ($logger !== NULL) {
// QZXW2HTML formatında günlük CalNPTgZQX
$log = $serializer->serialize($content, 'json');
$logger->write("réponse=$log\n");
}
}
}
Açıklamalar
- 34-48. satırlar: Bir Symfony serileştiricisinin örneklenmesi. Oluşturucu, dizi türünde iki parametre kabul eder;
- 36. satır: İlk dizi, nesnelerin serileştirilmesinde rol oynayan [ObjectNormalizer] türünde bir örneği içerir;
- 37-47. satırlar: İkinci dizi, serileştirme için kullanılan kodlayıcıları içerir. Aynı serileştirici ile çeşitli serileştirme türleri planlanabilir;
- satır 38-44: XML kodlayıcısı;
- 41. satır: Oluşturulan XML kodunun kökü belirlenir. Bu kod, <root>[autres balises XML]</root> biçiminde olacaktır;
- 42. satır: kodlamada UTF-8 karakterleri kullanılacaktır;
- satır 46; jSON kodlayıcısı. Bu, jSON formatında oluşturulan [logs.txt] dosyasında yanıt günlüğü için kullanılacaktır;
- 50. satır: müşteriye gönderilen yanıtın gövdesi XML formatında serileştirilir;
- satır 52: parametre olarak alınan başlıklara (satır 30), müşteriye XML belgesinin gönderildiğini belirten HTTP başlığı eklenir;
- satır 54: üst sınıf tarafından müşteriye yanıtın fiilen gönderilmesi;
- satır 56-60: yanıtın jSON formatında günlüğe kaydedilmesi;
23.12.4. [Postman] Testleri
jSON'te olası tüm hata testlerini zaten yaptık. XML'te yapılacak başka bir şey yok. XML yanıtına ilişkin iki örnek gösteriyoruz:

Yukarıda:
- [1-3]'te, oturum açma isteği XML;
- [4-7]'te, sunucudan gelen XML yanıtı;
Bundan sonra, sunucudan gelen tüm yanıtlar XML formatında olacaktır. [Postman]'te daha önce kullanılmış olan tüm istekleri değiştirmeden yeniden kullanabiliriz ve her biri için XML yanıtını alırız. Örneğin, başarılı bir kimlik doğrulama işlemi yapalım:

Yukarıda:
- [1-3]'te, geçerli bir kimlik doğrulama isteği;
- [4-7] olarak, sunucudan gelen XML yanıtı;
23.12.5. [HtmlResponse] yanıtı
Oturum türü [html] olduğunda, yanıtı istemciye göndermek için [HtmlResponse] türünde bir nesne oluşturulur. Bu nesne, eylemi işleyen ikincil denetleyicinin döndürdüğü durum koduna bağlı olarak istemciye bir HTML akışı gönderir. Bu [état=>vue] eşleşmesi, [config.json] yapılandırma dosyasına şu şekilde kaydedilir:
"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"
Bu yapılandırma şu şekilde okunur: [‘nom de la vue’ => ‘états associés à cette vue’]
- 2. satır: ikincil denetleyici [700, 221, 400] tablosundaki bir durumu döndürdüyse, [vue-authentification.php] görünümü görüntülenmelidir;
- 3. satır: ikincil denetleyici [200, 300, 341, 350, 800] tablosundaki bir durumu döndürdüyse, [vue-calcul-impot.php] görünümü görüntülenmelidir;
- 4. satır: İkincil denetleyici [500, 600] tablosundaki bir durumu döndürdüyse, [vue-liste-simulations.php] görünümünü görüntülemek gerekir;
- 6. satır: İkincil denetleyici, önceki tabloların hiçbirinde bulunmayan bir durum döndürdüyse, [vue-erreurs.php] görünümünü görüntülemek gerekir;
Görünümler, projenin [Views] klasöründe toplanmıştır:

[HtmlResponse] sınıfının kodu şöyledir:
<?php
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Serializer\Encoder\JsonEncode;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
class HtmlResponse extends ParentResponse implements InterfaceResponse {
// $request isteği: istek işleniyor
// $session oturumu: web uygulamasının oturumu
// $config dizisi: uygulama yapılandırması
// int statusCode : yanıt durum kodu HTTP
// dizi $content : sunucu yanıtı
// dizi $headers : yanıta eklenecek HTTP başlıkları
// Logger $logger: günlükleri yazmak için kullanılan günlük kaydedici
public function send(
Request $request = NULL,
Session $session = NULL,
array $config,
int $statusCode,
array $content,
array $headers,
Logger $logger = NULL): void {
// Symfony serileştiricisinin hazırlanması
$serializer = new Serializer(
[
// nesnelerin serileştirilmesi için
new ObjectNormalizer()],
[
// yanıt günlüğünün serileştirilmesi için jSON
new JsonEncoder(new JsonEncode([JsonEncode::OPTIONS => JSON_UNESCAPED_UNICODE]))
]
);
// Yanıt HTML, denetleyici tarafından döndürülen durum koduna bağlıdır
$état = $content["état"];
// bir duruma bir görünüm karşılık gelir - bu görünüm uygulama yapılandırmasında aranır
// görünüm listesi
$vues = array_keys($config["vues"]);
$trouvé = false;
$i = 0;
// görünüm listesi taranır
while (!$trouvé && $i < count($vues)) {
// i numaralı görünüme ilişkili durumlar
$états = $config["vues"][$vues[$i]];
// Aranan durum, i numaralı görünüme ilişkin durumlar arasında yer alıyor mu?
if (in_array($état, $états)) {
// görüntülenen görünüm, i numaralı görünüm olacaktır
$vueRéponse = $vues[$i];
$trouvé = true;
}
// sonraki görünüm
$i++;
}
// Bulundu mu?
if (!$trouvé) {
// uygulamanın mevcut durumu için herhangi bir görünüm yoksa
// hata görünümü görüntülenir
$vueRéponse = $config["vue-erreurs"];
}
// görüntülenecek HTML görünümünü bir karakter dizesi olarak alın
ob_start();
require __DIR__ . "/../Views/$vueRéponse";
$html = ob_get_clean();
// başlıklarda HTML gönderileceğini belirtilir
$headers = array_merge($headers, ["content-type" => "text/html"]);
// ana sınıf, yanıtın fiili gönderimini üstlenir
parent::sendResponse($statusCode, $html, $headers);
// HTML olmadan yanıtın jSON formatında günlüğe kaydedilmesi
if ($logger !== NULL) {
// eylemi işleyen ikincil denetleyicinin yanıtının jSON formatında günlüğü
$log = $serializer->serialize($content, 'json');
$logger->write("réponse=$log\n");
}
}
}
Açıklamalar
- 32-41. satırlar: Bir Symfony serileştiricisi oluşturulur. Bu, eylemi işleyen denetleyicinin yanıtının (72-82. satırlar) jSON günlüğü için gereklidir;
- 42-57. satırlar: Uygulama yapılandırmasında görüntülenmesi gereken görünüm aranır. Bu, eylemi işleyen denetleyicinin döndürdüğü durum koduna bağlıdır. Bu kod [$content[‘état’]] içinde yer alır (43. satır);
- satır 42-61: bu duruma karşılık gelen görünüm aranır;
- satır 62-67: Eğer hiçbir görünüm bulunamazsa, HTML uygulaması için anormal bir durum kodu söz konusudur. Anormal durum kavramı daha sonra ayrıntılı olarak açıklanacaktır. Bu durumda, bir hata görünümü görüntülenir;
- 68-70. satırlar: Seçilen görünümün PHP kodunu yorumlayıp sonucu [$html] değişkenine atıyoruz (71. satır);
- bu kodun biraz açıklanması gerekiyor. Seçilen görünümün, bir web kimlik doğrulama formu içeren [vue-authentification.php] olduğunu varsayalım:
- 69. satır: [ob_start] işlevi, belgelerde “çıkış gecikmesi” olarak adlandırılan işlemi başlatır. print, require… gibi işlemlerle yazılan ve normalde hemen istemciye gönderilmesi gereken her şey, istemciye gönderilmeden bir çıkış tamponuna (ob=output buffer) aktarılır;
- 70. satır: [vue-authentification.php] görünümü yüklenir; bu, HTML dinamik görünümüdür ve PHP kodunu içerir. Ardından iki şey gerçekleşir:
- [vue-authentification.php] görünümündeki PHP kodu yüklenir ve yorumlanır. Sonuçta, sadece HTML kodunu, hatta CSS ve JavaScript'i içeren, ancak artık PHP içermeyen ve [vue-authentification.html] olarak adlandıracağımız bir görünüm elde edilir;
- bu HTML kodu normalde istemciye gönderilir. Aslında bu durum, PHP yorumlayıcısı tarafından karşılaşılan ve PHP kodu olmayan tüm metinler için geçerlidir. Çıkış gecikmesi nedeniyle, bu HTML kodu müşteriye gönderilmeden çıkış tamponuna yerleştirilir;
- 71. satır: [ob_get_clean] işlevi iki şey yapar:
- çıkış tamponunun içeriğini, yani oraya yerleştirilen [vue-authentification.html] sayfasını [$html] değişkenine aktarır;
- çıkış tamponunu boşaltır. Çıkış tamponu açısından ise sanki hiçbir şey olmamış gibi devam eder. Ayrıca, istemci hâlâ hiçbir şey almamıştır;
- 70. satır: Burada, [Responses] klasöründe bulunan [HtmlResponse] sınıfı yürütülmektedir. Görünümü bulmak için bir üst seviyeye, yani [..] klasörüne çıkıp ardından [Views] klasörüne geçmek gerekir. [__DIR__], çalışmakta olan komut dosyasının bulunduğu klasörün mutlak adıdır; örneğimizde bu, [C:/myprograms/laragon-lite/www/php7/scripts-web/impots/13/Responses] klasörüdür;
- 73. satır: Parametre olarak alınan HTTP başlıklarına (29. satır), müşteriye HTML gönderileceğini bildiren başlığı ekliyoruz;
- 75. satır: üst sınıftan, yanıtın müşteriye fiilen gönderilmesini talep edilir;
- 77-81. satırlar: Devam eden eylemi işleyen ikincil denetleyici tarafından sağlanan [$content] yanıtı, jSON olarak günlüğe kaydedilir;
23.12.6. [Postman] Testleri
Oturumun HTML modunu tam olarak test etmek için tüm görünümleri gözden geçirmemiz gerekir. Bunu daha sonra yapacağız. Şu anda şu testi gerçekleştireceğiz:
Yapılandırma dosyasındaki görünüm listesine bir göz atalım:
"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"
Yukarıdaki durum kodlarından bazılarının ortaya çıktığı bağlamı, gerçekleştirilen [Postman] testlerine bakarak bulabiliriz:

[700] durum kodunun, [init-session] eyleminin başarılı bir sonucu olan [2]'e ait olduğu görülüyor. Yukarıda jSON yanıtı görülüyor, ancak bu yanıt XML veya HTML türünde de olabilir. Test edilecek olan sonuncu durumdur. Yapılandırma dosyasına göre, HTML yanıtını oluşturan [vue-authentification.php] görünümüdür. Kontrol edelim.

Yukarıda:
- [1-3]'te, bir HTML oturumu başlatılıyor. Dolayısıyla bir HTML yanıtı bekleniyor;
- [4-8]'te, sunucudan gelen HTML yanıtı;
- [8] sekmesi, alınan HTML kodunun önizlemesini sağlar;

- [8-9]'te, HTML görünümünün önizlemesi;
23.13. HTML web uygulaması
23.13.1. Görünümlerin tanıtımı
HTML web uygulaması dört görünüm kullanacaktır:
Kimlik doğrulama görünümü:

Vergi hesaplama görünümü:

Simülasyon listesi görünümü:

Beklenmedik hatalar görünümü:

Bu görünümleri sırayla açıklayacağız.
23.13.2. Kimlik doğrulama görünümü
23.13.2.1. Görünümün tanıtımı
Kimlik doğrulama görünümü şu şekildedir:

Görünüm, “parça” olarak adlandıracağımız iki öğeden oluşur:
- [1] parçası, [v-bandeau.php] komut dosyası tarafından oluşturulur;
- [2] parçası, [v-authentification.php] komut dosyası tarafından oluşturulur;
Kimlik doğrulama sayfası, aşağıdaki [vue-authentification.php] sayfası tarafından oluşturulur:
<?php
// sayfanın test verileri
// sayfa verileri $page içinde kapsüllenir
…
?>
<!doctype html>
<html lang="fr">
<head>
<!-- Gerekli meta etiketleri -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<title>Application impots</title>
</head>
<body>
<div class="container">
<!-- 1 satır ve 12 sütunlu başlık -->
<?php require "v-bandeau.php"; ?>
<!-- 9 sütunlu kimlik doğrulama formu -->
<div class="row">
<div class="col-md-9">
<?php require "v-authentification.php" ?>
</div>
</div>
<?php
// hata durumunda bir hata uyarısı görüntülenir
if ($modèle->error) {
print <<<EOT
<div class="row">
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
Les erreurs suivantes se sont produites :
<ul>$modèle->erreurs</ul>
</div>
</div>
</div>
EOT;
}
?>
</div>
</body>
</html>
Açıklamalar
- 7. satır: HTML belgesi bu satırla başlar;
- 8-44. satırlar: HTML sayfası, <html> </html> etiketleri arasında yer alır;
- 9-16. satırlar: HTML belgesinin başlığı (head);
- 11. satır: <meta charset> etiketi, belgenin UTF-8 kodlamasında olduğunu belirtir;
- 12. satır: <meta name='viewport'> etiketi, görünümün ilk görüntülenişini belirler: ekranın tüm genişliği (width) üzerinde, ilk boyutunda (initial-scale) ve daha küçük bir ekran boyutuna uyum sağlamak için yeniden boyutlandırılmadan (shrink-to-fit);
- 14. satır: <link rel=’stylesheet’> etiketi, görünümün görünümünü belirleyen CSS dosyasını tanımlar. Burada CSS Bootstrap 4.1.3 [https://getbootstrap.com/docs/4.0/getting-started/introduction/] çerçevesini kullanıyoruz ;
- 15. satır: <title> etiketi, sayfanın başlığını belirler:

- 17-43. satırlar: Web sayfasının gövdesi <body></body> etiketleri arasında yer alır;
- 18-42. satırlar: <div> etiketi, görüntülenen sayfanın bir bölümünü sınırlar. Görünümde kullanılan [class] özniteliklerinin tümü, CSS Bootstrap çerçevesine atıfta bulunur. <div class=’container’> etiketi, bir Bootstrap konteynerini sınırlar;
- 20. satır: [v-bandeau.php] betiği eklenir. Bu betik, sayfanın [1] başlık çubuğunu oluşturur. Bunu yakında açıklayacağız;
- 22-26. satırlar: <div class=’row’> etiketi bir Bootstrap satırını sınırlar. Bu satırlar 12 sütundan oluşur;
- 23. satır: <div class=’col-md-9’> etiketi, 9 sütunluk bir bölümü sınırlar;
- 24. satır: Sayfadaki [2] kimlik doğrulama formunu görüntüleyen [v-authentification.php] betiği eklenmiştir. Bunu yakında açıklayacağız;
- 27. satır: <?php etiketi, HTML sayfasının içine PHP kodunu ekler. Bu kod, HTML sayfası görüntülenmeden önce çalıştırılır ve bu sayfayı değiştirebilir;
- 29. satır: Görüntülenen görünümün tüm dinamik verileri, [stdClass] türünde bir [$modèle] nesnesine kapsüllenecektir. Bu keyfi bir seçimdir. Aynı sonucu elde etmek için bunun yerine bir ilişkisel dizi de seçilebilirdi;
- 29. satır: Kullanıcı yanlış kimlik bilgilerini girerse kimlik doğrulama başarısız olur. Bu durumda, kimlik doğrulama görünümü bir hata mesajıyla birlikte yeniden görüntülenir. [$modèle→error] özniteliği, bu hata mesajının görüntülenip görüntülenmeyeceğini belirtir;
- satır 30-39: Bu sözdizimi, PHP <<<EOT sembolleri arasına yerleştirilen tüm metni yazar (30. satır – EOT=End Of Text yerine istenilen herhangi bir şey yazılabilir) ile 39. satırdaki EOT sembolü (30. satırda kullanılan sembolle aynı olmalıdır) arasına yerleştirilen tüm metni yazdırır. Sembol, 39. satırın 1. sütununa yazılmalıdır. Metin içinde iki EOT sembolü arasında yer alan PHP değişkenleri yorumlanır;
- 33-36. satırlar: pembe arka planlı bir alanı sınırlar (class="alert alert-danger") (33. satır);

- 34. satır: bir metin;
- 35. satır: HTML <ul> (sırasız liste) etiketi, madde işaretli bir liste görüntüler. Listenin her bir öğesi <li>öğe</li> sözdizimine sahip olmalıdır;
Bu koddan tanımlanması gereken dinamik öğeleri not edelim:
- [$modèle→error]: bir hata mesajı görüntülemek için;
- [$modèle→erreurs]: bir hata mesajları listesi (HTML teriminin anlamı dahilinde);
23.13.2.2. [v-bandeau.php] parçası
[v-bandeau.php] parçası, web uygulamasının tüm görünümlerinde üst başlığı görüntüler:

[v-bandeau.php] parçasının kodu şöyledir:
<!-- Bootstrap Jumbotron -->
<div class="jumbotron">
<div class="row">
<div class="col-md-4">
<img src="<?= $logo ?>" alt="Cerisier en fleurs" />
</div>
<div class="col-md-8">
<h1>
Calculez votre impôt
</h1>
</div>
</div>
</div>
Açıklamalar
- 2-13. satırlar: Başlık, Jumbotron [<div class="jumbotron">] türünde bir Bootstrap bölümü içinde yer almaktadır. Bu Bootstrap sınıfı, görüntülenen içeriği öne çıkarmak için özel bir şekilde biçimlendirir;
- 3-12. satırlar: bir Bootstrap satırı;
- 4-6. satırlar: satırın ilk dört sütununa bir [img] resmi yerleştirilmiştir;
- 5. satır: [<?= $logo ?>] sözdizimi, [<?php print $logo ?>] sözdizimiyle eşdeğerdir. Başka bir deyişle, [src] özniteliğinin değeri, PHP ve [$logo] değişkenlerinin değeri olacaktır;
- 7-11. satırlar: satırdaki diğer 8 sütun (toplamda 12 sütun olduğunu hatırlatırız), bir metni (9. satır) büyük harflerle (<h1>, 8-10. satırlar) yerleştirmek için kullanılacaktır;
Dinamik öğeler:
- [$logo]: Başlık çubuğunda görüntülenen resmin URL'i;
23.13.2.3. [v-authentification.php] parçası
[v-authentification .php] parçası, web uygulamasının kimlik doğrulama formunu görüntüler:

[v-authentification.php] parçasının kodu şöyledir:
<!-- form HTML - değerler [authentifier-utilisateur] eylemiyle gönderilir -->
<form method="post" action="main.php?action=authentifier-utilisateur">
<!-- başlık -->
<div class="alert alert-primary" role="alert">
<h4>Veuillez vous authentifier</h4>
</div>
<!-- Bootstrap formu -->
<fieldset class="form-group">
<!-- 1. satır -->
<div class="form-group row">
<!-- etiket -->
<label for="user" class="col-md-3 col-form-label">Nom d'utilisateur</label>
<div class="col-md-4">
<!-- metin giriş alanı -->
<input type="text" class="form-control" id="user" name="user"
placeholder="Nom d'utilisateur" value="<?= $modèle->login ?>">
</div>
</div>
<!-- 2. satır -->
<div class="form-group row">
<!-- etiket -->
<label for="password" class="col-md-3 col-form-label">Mot de passe</label>
<!-- metin giriş alanı -->
<div class="col-md-4">
<input type="password" class="form-control" id="password" name="password"
placeholder="Mot de passe">
</div>
</div>
<!-- 3. satırda [submit] tipi düğme-->
<div class="form-group row">
<div class="col-md-2">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</div>
</fieldset>
</form>
Açıklamalar
- 2-39. satırlar: <form> etiketi, HTML formunu sınırlar. Bu form genellikle aşağıdaki özelliklere sahiptir:
- giriş alanları tanımlar (17. ve 27. satırlardaki <input> etiketleri);
- [submit] türünde bir düğme içerir (satır 34); bu düğme, girilen değerleri [form] etiketinin [action] özniteliğinde belirtilen URL'e gönderir (2. satır) etiketinin [action] özniteliğinde belirtilen URL'e gönderir;
- burada, kullanıcı [Valider] düğmesine (satır 34) tıkladığında, tarayıcı formda girilen değerleri URL [main.php?action=authentifier-utilisateur]'e (satır 2) gönderecektir (satır 2);
- Gönderilen değerler, kullanıcının 17. ve 27. satırlardaki giriş alanlarına girdiği değerlerdir. Bu değerler [user=xx&password=yy] biçiminde gönderilecektir. [user, password] parametre adları, 17. ve 27. satırlardaki giriş alanlarının [name] özniteliklerinin adlarıdır;
- satır 5-7: mavi arka plan üzerinde bir başlık görüntülemek için bir Bootstrap bölümü:

- satır 10-37: bir Bootstrap formu. Formun tüm öğeleri belirli bir şekilde biçimlendirilecektir;
- 12-20. satırlar: formun ilk satırını tanımlar:
![]()
- 14. satır, [1] etiketini üç sütuna yayacak şekilde tanımlar. [label] etiketinin [for] özniteliği, bu etiketi 17. satırdaki giriş alanının [id] özniteliğine bağlar;
- 15-19. satırlar: giriş alanını dört sütunlu bir gruba yerleştirir;
- 17. satır: HTML [input] etiketi bir giriş alanını tanımlar. Bu etiketin birkaç parametresi vardır:
- [type=’text’]: Bu bir metin giriş alanıdır. Buraya herhangi bir şey yazılabilir;
- [class=’form-control’]: giriş alanı için Bootstrap stili;
- [id=’user’]: Giriş alanının kimliği. Bu kimlik genellikle CSS ve JavaScript kodu tarafından kullanılır;
- [name=’user’]: giriş alanının adı. Kullanıcı tarafından girilen değer, tarayıcı tarafından bu adla [user=xx] adresine gönderilir;
- [placeholder=’invite’]: Kullanıcı henüz hiçbir şey yazmadığında giriş alanında görüntülenen metin;
![]()
- [value=’valeur’]: Giriş alanı görüntülendiği anda, yani kullanıcı başka bir şey girmeden önce, bu alana ‘değer’ metni görüntülenecektir. Bu mekanizma, bir hata oluştuğunda hataya neden olan girişi görüntülemek için kullanılır. Burada bu değer, PHP [$modèle→login] değişkeninin değeri olacaktır;
- 21-30. satırlar: şifre girişi için benzer bir kod;
- 27. satır: [type=’password’], bir metin giriş alanı oluşturulmasını sağlar (istediğiniz herhangi bir şeyi yazabilirsiniz) ancak yazılan karakterler gizlenir:
![]()
- satır 32-36: [Valider] düğmesi için üçüncü bir satır;
- 34. satır: [type=submit] özniteliğine sahip olduğu için, bu düğmeye tıklandığında, daha önce açıklandığı gibi girilen değerler tarayıcı tarafından sunucuya gönderilir. CSS ve [class="btn btn-primary"] öznitelikleri mavi bir düğme görüntüler:

Son bir şeyi daha açıklamamız gerekiyor. 2. satırda, [action="main.php?action=authentifier-utilisateur"] özniteliği, eksik bir URL tanımlar (http://machine:port/chemin ile başlamaz). Örneğimizde, uygulamadaki tüm URL öğeleri [http://localhost/php7/scripts-web/impots/version-12/main.php?action=xx] biçimindedir. Kimlik doğrulama görünümü, çeşitli URL öğeleriyle elde edilecektir:
- [http://localhost/php7/scripts-web/impots/version-12/main.php?action=init-session&type=html];
- [http://localhost/php7/scripts-web/impots/version-12/main.php?action=authentifier-utilisateur]
Bu URL değerleri, [http://localhost/php7/scripts-web/impots/version-12] yolundaki bir [main.php] belgesini belirtir. Bu durum, söz konusu uygulamanın tüm URL değerleri için geçerli olacaktır. Girilen değerler gönderilirken, [action="main.php?action=authentifier-utilisateur"] parametresinin başına bu yol eklenecektir. Dolayısıyla bu değerler, URL [http://localhost/php7/scripts-web/impots/version-12/main.php?action=authentifier-utilisateur] adresine gönderilecektir.
23.13.2.4. Görsel testler
Görünümler, uygulamaya entegre edilmeden çok önce test edilebilir. Burada amaç, görünümlerin görsel yönünü test etmektir. Tüm test görünümlerini projenin [Tests] klasöründe toplayacağız:

[vue-authentification.php] görünümünü test etmek için, bu görünümün görüntüleyeceği veri modelini oluşturmamız gerekiyor:
<?php
// sayfanın test verileri
//
// görünüm şablonu hesaplanıyor
$modèle = getModelForThisView();
function getModelForThisView(): object {
// sayfa verileri $modèle içine kapsüllenir
$modèle = new \stdClass();
// kullanıcı kimliği
$modèle->login = "albert";
// hata listesi
$modèle->error = TRUE;
$erreurs = ["erreur1", "erreur2"];
// HTML hatalar listesi oluşturulur
$content = "";
foreach ($erreurs as $erreur) {
$content .= "<li>$erreur</li>";
}
$modèle->erreurs = $content;
// başlık resmi
$modèle->logo = "http://localhost/php7/scripts-web/impots/version-12/Tests/logo.jpg";
// şablonu oluşturuyoruz
return $modèle;
}
?>
<!-- HTML belgesi -->
<!doctype html>
<html lang="fr">
<head>
<!-- Gerekli meta etiketleri -->
…
</head>
<body>
….
</body>
</html>
Açıklamalar
- 1-5. satırlar: Kimlik doğrulama görünümü, [$modèle] nesnesi tarafından kontrol edilen dinamik bölümlere sahiptir. Bu nesneye görünüm modeli denir. MVC kısaltması için verilen iki tanımdan birine göre, burada MVC'in M'si yer almaktadır;
- 5. satır: Görünüm şablonu, [getModelForThisView] işlevi tarafından hesaplanır;
- 9. satır: görünüm modeli, [stdClass] türüne kapsüllenecektir;
- satır 10-22: kimlik doğrulama görünümünün dinamik öğeleri için test değerleri tanımlanır;
Görsel test NetBeans üzerinden yapılabilir:

Sonuçtan memnun kalana kadar bu görsel testlere devam edilir.
23.13.2.5. Görünüm modelinin hesaplanması
Görünümün görsel yapısı belirlendikten sonra, gerçek koşullar altında görünüm modelinin hesaplamasına geçilebilir. Bu görünüme yönlendiren durum kodlarını hatırlayalım. Bunlar yapılandırma dosyasında bulunur:
"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"
Dolayısıyla, kimlik doğrulama görünümünü görüntüleyen durum kodları [700, 221, 400]'tir. Bu kodların anlamını bulmak için, jSON uygulaması üzerinde gerçekleştirilen [Postman] testlerinden yararlanabiliriz:
- [init-session-json-700]: 700, başarılı bir [init-session] eyleminin ardından ortaya çıkan durum kodudur; bu durumda boş kimlik doğrulama formu görüntülenir;
- [authentifier-utilisateur-221]: 221, [authentifier-utilisateur] işleminin başarısızlıkla sonuçlanmasının ardından (kimlik bilgileri tanınmadı) verilen durum kodudur: bu durumda, düzeltilmesi için kimlik doğrulama formu görüntülenir;
- [fin-session-400]: 400, [fin-session] eyleminin başarılı sonuçlanmasının durum kodudur: bu durumda boş kimlik doğrulama formu görüntülenir;
Artık kimlik doğrulama formunun hangi durumlarda görüntülenmesi gerektiğini bildiğimize göre, [vue-authentification.php]'te form şablonunu hesaplayabiliriz:

[vue-authentification.php] görünüm şablonunu hesaplayan kod şöyledir:
<?php
// Aşağıdaki değişkenler devralınıyor
// İstek $request: mevcut istek
// Oturum $session: uygulama oturumu
// dizi $config: uygulamanın yapılandırması
// dizi $content: denetleyicinin yanıtı
//
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
// görünüm şablonu hesaplanıyor
$modèle = getModelForThisView($request, $session, $config, $content);
function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
// sayfa verileri $modèle içinde kapsülleniyor
$modèle = new stdClass();
// uygulamanın durumu
$état = $content["état"];
// model, duruma bağlıdır
switch ($état) {
case 700:
case 400:
// boş formun görüntülenmesi durumu
$modèle->login = "";
// görüntülenecek hata yok
$modèle->error = FALSE;
break;
case 221:
// kimlik doğrulama hatası
// Başlangıçta girilen kullanıcı yeniden görüntüleniyor
$modèle->login = $request->request->get("user");
// görüntülenecek bir hata var
$modèle->error = TRUE;
// HTML hata mesajları listesi - burada tek bir hata var
$modèle->erreurs = "<li>Echec de l'authentification</li>";
}
// sonuç
return $modèle;
}
?>
<!-- HTML belgesi -->
<!doctype html>
<html lang="fr">
<head>
…
</head>
<body>
…
</body>
</html>
Açıklamalar
- 3-6. satırlar: [HtmlResponse] sınıfından miras alınan değişkenler çağrılır; bu sınıf, [require] aracılığıyla [vue-authentification.php] görünümünü görüntüler;
- 9-10. satırlar: Görünüm kodunda kullanılan Symfony sınıfları;
- satır 15-40: [getModelForThisView] işlevi, görünüm modelini hesaplamakla görevlidir;
- 19. satır: mevcut eylemi işleyen denetleyici tarafından döndürülen durum kodunu alıyoruz;
- 21-37. satırlar: Şablon, bu durum koduna bağlıdır;
- 22-28. satırlar: Boş bir kimlik doğrulama formunun görüntülenmesi gereken durum;
- 29-37. satırlar: kimlik doğrulama hatası durumu: kullanıcı için girilen kimlik bilgisi görüntülenir ve bir hata mesajı gösterilir. Kullanıcı klavyeyi kullanarak başka bir kimlik doğrulama denemesi yapabilir;
[v-bandeau.php] başlığı için özel bir şablon yazılmıştır:
<?php
// logo
$scheme = $request->server->get('REQUEST_SCHEME'); // http
$host = $request->server->get('SERVER_NAME'); // localhost
$port = $request->server->get('SERVER_PORT'); // 80
$uri = $request->server->get('REQUEST_URI'); // /php7/scripts-web/impots/version-12/main.php?action=xxx
$champs = [];
preg_match("/(.+)\/.+?$/", $uri, $champs);
$root = $champs[1]; // /php7/scripts-web/impots/version-12
$modèle->logo = "$scheme://$host:$port$root/Views/logo.jpg"; // http://localhost:80/php7/scripts-web/impots/version-12/Views/logo.jpg
?>
<!-- Bootstrap Jumbotron -->
<div class="jumbotron">
<div class="row">
<div class="col-md-4">
<img src="<?= $modèle->logo ?>" alt="Cerisier en fleurs" />
</div>
<div class="col-md-8">
<h1>
Calculez votre impôt
</h1>
</div>
</div>
</div>
Açıklamalar
- 16. satırda, banner logosundaki URL değerini temsil eden [$modèle→logo] değişkeni kullanılmaktadır. Bu değişkeni uygulamanın dört görünümü için dört kez hesaplamak yerine, hesaplama [v-bandeau.php] parçacığına faktörleştirilmiştir;
- 1-11. satırlar, [$request→server] sunucu ortamında bulunan bilgilerden URL ve [http://localhost:80/php7/scripts-web/impots/version-12/Views/logo.jpg]'in nasıl oluşturulacağını göstermektedir;
23.13.2.6. [Postman] Testleri
Kimlik doğrulama ekranını görüntüleyen [700, 221, 400] kodlarını üreten istekleri daha önce oluşturmuştuk. Bunları tekrar hatırlayalım:
- [init-session-html-700]: 700, [init-session] eyleminin başarıyla tamamlanmasının ardından alınan durum kodudur; bu durumda boş kimlik doğrulama formu görüntülenir;
- [authentifier-utilisateur-221]: 221, [authentifier-utilisateur] işleminin başarısız olması durumunda (kimlik bilgileri tanınmadı) verilen durum kodudur: bu durumda, düzeltilmesi için kimlik doğrulama formu görüntülenir;
- [fin-session-400]: 400, [fin-session] eyleminin başarılı sonuçlanmasının durum kodudur: bu durumda boş kimlik doğrulama formu görüntülenir;
Bunları tekrar kullanıp kimlik doğrulama ekranının düzgün bir şekilde görüntülenip görüntülenmediğini kontrol etmek yeterlidir. Burada sadece iki test gösterilecektir:
- [init-session-html-700]: HTML oturumunun başlangıcı;

- [authentifier-utilisateur-221]: [x, x] kullanıcısının kimlik doğrulaması;

Yukarıda:
- istek, [user=x&password=x] dizesini göndermişti;
- [4]'te bir hata mesajı görüntülenir;
- [3]'te, hatalı kullanıcı yeniden görüntülendi;
23.13.2.7. Sonuç
Diğer görünümleri yazmadan [vue-authentification.php] görünümünü test edebildik. Bunun mümkün olmasının nedeni şudur:
- tüm denetleyiciler yazılmış;
- [Postman], görünümler olmadan sunucuya istek göndermemize olanak tanır. Denetleyicileri yazarken, bunu herkesin yapabileceğinin farkında olmak gerekir. Bu nedenle, hiçbir görünümün izin vermeyeceği istekleri işlemeyi göze almalıyız. Bu istekler, [Postman]'te elle oluşturulur. Asla önceden “bu istek imkansız” diye düşünmemeliyiz. Kontrol etmek gerekir;
23.13.3. Vergi hesaplama görünümü
23.13.3.1. Görünümün tanıtımı
Vergi hesaplama görünümü şöyledir:

Görünüm üç bölümden oluşur:
- 1: Üst başlık, daha önce tanıtılan [v-bandeau.php] parçası tarafından oluşturulur;
- 2: [v-calcul-impot.php] parçası tarafından oluşturulan vergi hesaplama formu;
- 3: [v-menu.php] parçası tarafından oluşturulan, iki bağlantı içeren bir menü;
Vergi hesaplama sayfası, aşağıdaki [vue-calcul-impot.php] komut dosyası tarafından oluşturulur:

<?php
// aşağıdaki değişkenler devralınır
// $request isteği: mevcut istek
// $session oturumu: uygulamanın oturumu
// dizi $config: uygulamanın yapılandırması
// dizi $content: eylemi işleyen denetleyicinin yanıtı
//
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
// görünüm modelini hesaplıyoruz
$modèle = getModelForThisView($request, $session, $config, $content);
function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
// sayfa verileri $modèle içinde kapsülleniyor
$modèle = new \stdClass();
…
// model görüntülenir
return $modèle;
}
?>
<!-- HTML belgesi -->
<!doctype html>
<html lang="fr">
<head>
<!-- Gerekli meta etiketleri -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<title>Application impots</title>
</head>
<body>
<div class="container">
<!-- başlık çubuğu -->
<?php require "v-bandeau.php"; ?>
<!-- iki sütunlu satır -->
<div class="row">
<!-- menü -->
<div class="col-md-3">
<?php require "v-menu.php" ?>
</div>
<!-- hesaplama formu -->
<div class="col-md-9">
<?php require "v-calcul-impot.php" ?>
</div>
</div>
<!-- başarı durumu -->
<?php
if ($modèle->success) {
// başarı uyarısı görüntülenir
print <<<EOT1
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-9">
<div class="alert alert-success" role="alert">
$modèle->impôt</br>
$modèle->décôte</br>\n
$modèle->réduction</br>\n
$modèle->surcôte</br>\n
$modèle->taux</br>\n
</div>
</div>
</div>
EOT1;
}
?>
<?php
if ($modèle->error) {
// 9 sütunlu hata listesi
print <<<EOT2
<div class="row">
<div class="col-md-3">
</div>
<div class="col-md-9">
<div class="alert alert-danger" role="alert">
L'erreur suivante s'est produite :
<ul>$modèle->erreurs</ul>
</div>
</div>
</div>
EOT2;
}
?>
</div>
</body>
</html>
Yorumlar
- Yalnızca henüz karşılaşmadığımız yenilikler hakkında yorum yapıyoruz;
- 37. satır: Görünümün üst başlığının, görünümün ilk Bootstrap satırına eklenmesi;
- 41-43. satırlar: Görünümün ikinci Bootstrap satırında üç sütun kaplayacak menünün eklenmesi;
- satır 45-47: görünümün ikinci Bootstrap satırında dokuz sütun kaplayacak vergi hesaplama formunun eklenmesi;
- satır 51-69: [$modèle→success=TRUE] vergi hesaplaması başarılı olursa, vergi hesaplama sonucu yeşil bir çerçeve içinde görüntülenir (satır 59-65). Bu çerçeve, görünümün üçüncü Bootstrap satırında (satır 54) yer alır ve üç boş sütunun (satır 55-57) sağında dokuz sütun (satır 58) kaplar. Dolayısıyla bu çerçeve, vergi hesaplama formunun hemen altında yer alacaktır;
- satır 71-87: [$modèle→error=TRUE] vergi hesaplaması başarısız olursa, pembe bir çerçeve içinde bir hata mesajı görüntülenir (satır 80-83). Bu çerçeve, görünümün üçüncü Bootstrap satırında (satır 75) yer alır ve üç boş sütunun (satır 76-78) sağında dokuz sütun (satır 79) kaplar. Dolayısıyla bu çerçeve, vergi hesaplama formunun hemen altında yer alacaktır;
23.13.3.2. [v-calcul-impot.php] parçası
[v-calcul-impot.php] parçası, web uygulamasının kimlik doğrulama formunu görüntüler:

[v-calcul-impot.php] parçasının kodu şöyledir:
<!-- gönderilen HTML formu -->
<form method="post" action="main.php?action=calculer-impot">
<!-- mavi arka planlı 12 sütunlu mesaj -->
<div class="col-md-12">
<div class="alert alert-primary" role="alert">
<h4>Remplissez le formulaire ci-dessous puis validez-le</h4>
</div>
</div>
<!-- form öğeleri -->
<fieldset class="form-group">
<!-- 9 sütunlu ilk satır -->
<div class="row">
<!-- 4 sütunlu başlık -->
<legend class="col-form-label col-md-4 pt-0">Etes-vous marié(e) ou pacsé(e)?</legend>
<!-- 5 sütunlu radyo düğmeleri-->
<div class="col-md-5">
<div class="form-check">
<input class="form-check-input" type="radio" name="marié" id="gridRadios1" value="oui" <?= $modèle->checkedOui ?>>
<label class="form-check-label" for="gridRadios1">
Oui
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="marié" id="gridRadios2" value="non" <?= $modèle->checkedNon ?>>
<label class="form-check-label" for="gridRadios2">
Non
</label>
</div>
</div>
</div>
<!-- 9 sütunlu ikinci satır -->
<div class="form-group row">
<!-- 4 sütunlu etiket -->
<label for="enfants" class="col-md-4 col-form-label">Nombre d'enfants à charge</label>
<!-- 5 sütunlu çocuk sayısı sayısal giriş alanı -->
<div class="col-md-5">
<input type="number" min="0" step="1" class="form-control" id="enfants" name="enfants" placeholder="Nombre d'enfants à charge" value="<?= $modèle->enfants ?>">
</div>
</div>
<!-- 9 sütunlu üçüncü satır -->
<div class="form-group row">
<!-- 4 sütunluk açıklama alanı -->
<label for="salaire" class="col-md-4 col-form-label">Salaire annuel</label>
<!-- 5 sütunlu maaş için sayısal giriş alanı -->
<div class="col-md-5">
<input type="number" min="0" step="1" class="form-control" id="salaire" name="salaire" placeholder="Salaire annuel" aria-describedby="salaireHelp" value="<?= $modèle->salaire ?>">
<small id="salaireHelp" class="form-text text-muted">Arrondissez à l'euro inférieur</small>
</div>
</div>
<!-- dördüncü satır, 5 sütunlu [submit] düğmesi -->
<div class="form-group row">
<div class="col-md-5">
<button type="submit" class="btn btn-primary">Valider</button>
</div>
</div>
</fieldset>
</form>
Açıklamalar
- 2. satır: HTML formu, [method] özniteliği aracılığıyla URL [main.php?action=calculer-impot] ([action] özniteliği). Gönderilen değerler, giriş alanlarının değerleri olacaktır:
- işaretlenen radyo düğmesinin değeri şu şekilde olacaktır:
- [marié=oui] (eğer [Oui] radyo düğmesi işaretlenmişse (satır 16-22). [marié], 18. satırdaki [name] özniteliğinin değeridir; [oui] ise 18. satırdaki [value] özniteliğinin değeridir;
- [marié=non], [Non] radyo düğmesi işaretlendiğinde (satır 23-28). [marié], 24. satırdaki [name] özniteliğinin değeridir; [non], 24. satırdaki [value] özniteliğinin değeridir;
- 37. satırdaki sayısal giriş alanının değeri [enfants=xx] biçimindedir; burada [enfants], 37. satırdaki [name] özniteliğinin değeridir, ve [xx], kullanıcı tarafından klavyeyle girilen değerdir;
- 46. satırdaki sayısal giriş alanının değeri [salaire=xx] biçimindedir; burada [salaire], 46. satırdaki [name] özniteliğinin değeridir, ve [xx], kullanıcı tarafından klavyeden girilen değerdir;
- işaretlenen radyo düğmesinin değeri şu şekilde olacaktır:
Sonuç olarak, gönderilen değer [marié=xx&enfants=yy&salaire=zz] şeklinde olacaktır.
- Girilen değerler, kullanıcı 53. satırdaki [submit] türündeki düğmeye tıkladığında gönderilecektir;
- 16-30. satırlar: iki radyo düğmesi:
![]()
Bu iki radyo düğmesi, aynı [name] özniteliğine sahip oldukları için (satır 18, 24) aynı radyo düğmesi grubuna aittir. Tarayıcı, bir radyo düğmesi grubunda herhangi bir anda yalnızca bir tanesinin işaretli olmasını sağlar. Dolayısıyla, birine tıklandığında, daha önce işaretli olan diğerinin seçimi kaldırılır;
- [type="radio"] özniteliği nedeniyle (18. ve 24. satırlar) bunlar radyo düğmeleridir;
- form görüntülendiğinde (veri girilmeden önce), radyo düğmelerinden birinin işaretli olması gerekir: bunun için ilgili <input type="radio"> etiketine [checked=’checked’] özniteliğini eklemek yeterlidir. Bu, dinamik değişkenlerle gerçekleştirilir:
- 18. satırda [<?= $modèle->checkedOui ?>];
- 24. satırda [<?= $modèle->checkedNon ?>];
Bu değişkenler, görünüm şablonunun bir parçası olacaktır.
- 37. satır: minimum değeri 0 olan [type="number"] sayısal giriş alanı. Güncel tarayıcılarda bu, kullanıcının yalnızca >=0 olan bir sayı girebileceği anlamına gelir. Aynı güncel tarayıcılarda, giriş, yukarı veya aşağı yönde tıklanabilen bir kaydırma çubuğu ile yapılabilir. 37. satırdaki [step="1"] özniteliği, kaydırma çubuğunun 1 birimlik adımlarla çalışacağını belirtir. Bunun sonucu olarak, kaydırma çubuğu yalnızca 0'dan n'ye kadar 1'er birimlik adımlarla artan tamsayıları değer olarak kabul edecektir. Manuel giriş için bu, virgül içeren sayıların kabul edilmeyeceği anlamına gelir;
![]()
- 37. satır: Bazı ekranlarda, çocuk giriş alanı bu alana en son girilen değerle önceden doldurulmalıdır. Bunun için, giriş alanında görüntülenecek değeri belirleyen [value] özniteliği kullanılır. Bu değer dinamik olacak ve [$modèle→enfants] değişkeni tarafından oluşturulacaktır;
- 46. satır: Maaş girişi için de çocuk girişi ile aynı açıklamalar geçerlidir;
- satır 53: [submit] türündeki düğme, POST'i tetikleyerek girilen değerleri URL ve [main.php?action=calculer-impot]'e aktarır;

23.13.3.3. [v-menu.php] parçası
Bu parça, vergi hesaplama formunun solunda bir menü görüntüler:

Bu parçanın kodu şöyledir:
<!-- Bootstrap menüsü -->
<nav class="nav flex-column">
<?php
// bağlantı listesinin görüntülenmesi HTML
foreach($modèle->optionsMenu as $texte=>$url){
print <<<EOT3
<a class="nav-link" href="$url">$texte</a>
EOT3;
}
?>
</nav>
Açıklamalar
- 2-11. satırlar: HTML [nav] etiketleri, diğer belgelere yönlendirme bağlantıları içeren HTML belgesinin bir bölümünü çerçeveler;
- 7. satır: HTML [a] etiketi bir gezinme bağlantısı ekler:
- [$url]: URL bağlantısına tıklandığında yönlendirilen hedeftir. Bu durumda tarayıcı tarafından bir [GET $url] işlemi gerçekleştirilir. [$url], göreceli bir URL ise, tarayıcının adres çubuğunda o anda görüntülenen URL'in kök kısmı bu bağlantının başına eklenir. Dolayısıyla, tarayıcının mevcut URL bağlantısı [http://chemin/main.php?paramètres] türündeyken, [1] bağlantısını elde etmek için şu bağlantı oluşturulur:
- 5. satır: Parçanın [$modèle→optionsMenu] şablonu şu biçimde bir tablo olacaktır:
[‘ Liste des simulations’=>’main.php?action=liste-simulations’,
‘ Fin de session’=>’main.php?action=fin-session’]
- 2. ve 7. satırlar: CSS ve [nav, flex-column, nav-link] sınıfları, menüye görünümünü veren Bootstrap sınıflarıdır;
23.13.3.4. Görsel test
Bu farklı öğeleri [Tests] klasöründe bir araya getiriyoruz ve [vue-calcul-impot.php] görünümü için bir test şablonu oluşturuyoruz:

[vue-calcul-impot] görünümünün veri modeli şu şekilde olacaktır:
<?php
// sayfanın test verileri
//
// görünüm şablonu hesaplanıyor
$modèle = getModelForThisView();
function getModelForThisView(): object {
// sayfa verilerinin $modèle içinde kapsüllenmesi
$modèle = new \stdClass();
// form
$modèle->checkedOui = "";
$modèle->checkedNon = 'checked="checked"';
$modèle->enfants = 2;
$modèle->salaire = 300000;
// başarı mesajı
$modèle->success = TRUE;
$modèle->impôt = "Montant de l'impôt : 1000 euros";
$modèle->décôte = "Décôte : 15 euros";
$modèle->réduction = "Réduction : 20 euros";
$modèle->surcôte = "Surcôte : 0 euros";
$modèle->taux = "Taux d'imposition : 14 %";
// hata mesajı
$modèle->error = TRUE;
$erreurs = ["erreur1", "erreur2"];
// hataların bir listesi oluşturulur HTML
$content = "";
foreach ($erreurs as $erreur) {
$content .= "<li>$erreur</li>";
}
$modèle->erreurs = $content;
// menü
$modèle->optionsMenu = [
'Simülasyon listesi' => 'main.php?action=simülasyon-listesi',
'Oturum sonu' => 'main.php?action=oturum-sonu'];
// başlık resmi
$modèle->logo = "http://localhost/php7/scripts-web/impots/version-12/Tests/logo.jpg";
// şablonu oluşturuyoruz
return $modèle;
}
?>
<!-- HTML belgesi -->
<!doctype html>
<html lang="fr">
<head>
…
</head>
<body>
…
</body>
</html>
Açıklamalar
- 7-39. satırlar: [vue-calcul-impot.php] görünümünün ve [v-calcul-impot.php] ile [v-menu.php] parçalarının tüm dinamik kısımlarını başlatıyoruz;
[vue-calcul-impot.php] görünümünü test ediyoruz:

Aşağıdaki sonuç elde edilir:

Görsel olarak elde edilen sonuç bizi tatmin edene kadar bu görünüm üzerinde çalışıyoruz. Ardından, yazım aşamasında olan web uygulamasına bu görünümü entegre edebiliriz.
23.13.3.5. Görünüm modelinin hesaplanması

Görünümün görsel yapısı belirlendikten sonra, gerçek koşullarda görünüm modelinin hesaplanmasına geçilebilir. Bu görünüme yönlendiren durum kodlarını hatırlayalım. Bunlar yapılandırma dosyasında bulunur:
"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"
Dolayısıyla, kimlik doğrulama ekranının görüntülenmesini sağlayan durum kodları [200, 300, 341, 350, 800]’tir. Bu kodların anlamını öğrenmek için, jSON uygulaması üzerinde gerçekleştirilen [Postman] testlerinden yararlanılabilir:
- [authentifier-utilisateur-200]: 200, [authentifier-itilisateur] eyleminin başarıyla tamamlanmasının ardından verilen durum kodudur; bu durumda boş vergi hesaplama formu görüntülenir;
- [calculer-impot-300]: 300, [calculer-impot] işleminin başarıyla tamamlanmasının ardından verilen durum kodudur. Bu durumda, girilen veriler ve vergi tutarı ile birlikte hesaplama formu görüntülenir. Kullanıcı daha sonra başka bir hesaplama yapabilir;
- [fin-session-400]: 400, [fin-session] işleminin başarıyla tamamlanmasının ardından verilen durum kodudur: bu durumda, boş kimlik doğrulama formu görüntülenir;
- [341] durum kodu, geçerli bir vergi hesaplaması için elde edilen koddur, ancak SGBD sunucusuna bağlantı kurulamaması bir hataya neden olur;
- [350] durum kodu, geçerli bir vergi hesaplaması için elde edilen koddur; ancak [Redis] sunucusuna bağlantı kurulamaması bir hataya neden olur;
- [800] durum kodu daha sonra sunulacaktır. Henüz bu kodla karşılaşmadık;
- Burada, kullanıcının güncel bir tarayıcı kullandığı varsayılmıştır. Dolayısıyla, incelenen formda, [enfants, salaire] giriş alanlarına negatif sayılar, sayısal olmayan karakter dizileri veya ondalık sayılar girilemez. Daha eski tarayıcılarda ise bu mümkün olabilir. Bu hataları beklenmedik hatalar olarak ele alacağız ve [vue-erreurs] görünümünü göstereceğiz;
Artık vergi hesaplama formunun ne zaman görüntülenmesi gerektiğini bildiğimize göre, [vue-calcul-impot.php]'te form şablonunu hesaplayabiliriz:
<?php
// aşağıdaki değişkenler devralınıyor
// $request isteği: mevcut istek
// Oturum $session: uygulamanın oturumu
// $config dizisi: uygulamanın yapılandırması
// dizisi $content: eylemi işleyen denetleyicinin yanıtı
//
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
// görünüm şablonu hesaplanıyor
$modèle = getModelForThisView($request, $session, $config, $content);
function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
// sayfa verileri $modèle içinde kapsülleniyor
$modèle = new \stdClass();
// uygulamanın durumu
$état = $content["état"];
// model, duruma bağlıdır
switch ($état) {
case 200 :
case 800:
// boş bir formun ilk görüntülenmesi
$modèle->success = FALSE; $modèle->errror = FALSE;
$modèle->checkedNon = 'checked="checked"';
$modèle->checkedOui = "";
$modèle->enfants = "";
$modèle->salaire = "";
break;
case 300:
// hesaplamanın başarılı olması - sonucun görüntülenmesi
$modèle->success = TRUE;
$modèle->error = FALSE;
$modèle->impôt = "Montant de l'impôt : {$content["réponse"]["impôt"]} euros";
$modèle->décôte = "Décôte : {$content["réponse"]["décôte"]} euros";
$modèle->réduction = "Réduction : {$content["réponse"]["réduction"]} euros";
$modèle->surcôte = "Surcôte : {$content["réponse"]["surcôte"]} euros";
$modèle->taux = "Taux d'imposition : " . ($content["réponse"]["taux"] * 100) . " %";
// Girilen değerlerle formun yeniden yüklenmesi
$modèle->checkedOui = $request->request->get("marié") === "oui" ? 'checked="checked"' : "";
$modèle->checkedNon = $request->request->get("marié") === "oui" ? "" : 'checked="checked"';
$modèle->enfants = $request->request->get("enfants");
$modèle->salaire = $request->request->get("salaire");
break;
case 341:
// veritabanı HS
case 350:
// Redis sunucusu HS
// girdilen değerlerle yenilenen form
$modèle->checkedOui = $request->request->get("marié") === "oui" ? 'checked="checked"' : "";
$modèle->checkedNon = $request->request->get("marié") === "oui" ? "" : 'checked="checked"';
$modèle->enfants = $request->request->get("enfants");
$modèle->salaire = $request->request->get("salaire");
// hata
$modèle->success = FALSE;
$modèle->error = TRUE;
$modèle->erreurs = "<li>{$content["réponse"]}</li>";
break;
}
//menü
$modèle->optionsMenu = [
"Liste des simulations" => "main.php?action=lister-simulations",
"Fin de session" => "main.php?action=fin-session"];
// şablon geri yükleniyor
return $modèle;
}
?>
<!-- belge HTML -->
<!doctype html>
<html lang="fr">
<head>
…
<title>Application impots</title>
</head>
<body>
…
</body>
</html>
Açıklamalar
- 22-30. satırlar: boş bir formun görüntülenmesi;
- satır 31-45: vergi hesaplamasının başarılı olduğu durum. Girilen değerler ve vergi tutarı yeniden görüntülenir;
- satır 46-59: [Redis] veya [MySQL] sunucularından birinin kullanılamaması nedeniyle vergi hesaplamasının başarısız olması durumu;
- satır 62-64: menüdeki iki seçeneğin hesaplanması;
23.13.3.6. [Postman] Testleri
[calculer-impot-300] testi, 300 durum kodunu almamızı sağlar. Bu, vergi hesaplamasının başarılı olduğunu gösterir:

- [3]'te, [2] sonucuna yol açan değerler;
Bir hata durumunu deneyelim: [Redis] sunucusunun erişilememesi nedeniyle oluşan [350] hatası:

23.13.4. Simülasyon listesi görünümü
23.13.4.1. Görünümün tanıtımı
Simülasyon listesini gösteren görünüm şöyledir:

[vue-liste-simulations] komut dosyası tarafından oluşturulan görünüm üç bölümden oluşur:
- 1: Üst başlık, daha önce tanıtılan [v-bandeau.php] parçası tarafından oluşturulur;
- 2: [v-liste-simulations.php] parçası tarafından oluşturulan simülasyon tablosu;
- 3: [v-menu.php] parçası tarafından oluşturulan, iki bağlantı içeren bir menü;
Simülasyonlar sayfası, aşağıdaki [vue-liste-simulations.php] komut dosyası tarafından oluşturulur:

<?php
// görünüm şablonu hesaplanıyor
$modèle = getModelForThisView();
function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
// sayfa verilerini $modèle içinde kapsülleme
$modèle = new \stdClass();
…
// şablon oluşturulur
return $modèle;
}
?>
<!-- HTML belgesini oluşturur -->
<!doctype html>
<html lang="fr">
<head>
<!-- Gerekli meta etiketleri -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<title>Application impots</title>
</head>
<body>
<div class="container">
<!-- başlık çubuğu -->
<?php require "v-bandeau.php"; ?>
<!-- iki sütunlu düzen -->
<div class="row">
<!-- üç sütunlu menü-->
<div class="col-md-3">
<?php require "v-menu.php" ?>
</div>
<!-- 9 sütunlu simülasyon listesi-->
<div class="col-md-9">
<?php require "v-liste-simulations.php" ?>
</div>
</div>
</div>
</body>
</html>
Açıklamalar
- 28. satır: [1] uygulamasının başlık çubuğunun eklenmesi;
- 33. satır: [2] menüsünün eklenmesi. Bu menü, başlığın altında üç sütun halinde görüntülenecektir;
- 37. satır: [3] simülasyon tablosunun eklenmesi. Bu tablo, başlığın altında ve menünün sağında dokuz sütun halinde görüntülenecektir;
Bu görünümdeki üç parçadan ikisini daha önce yorumlamıştık:
[v-liste-simulations.php] parçası şöyledir:
<!-- mavi arka planlı mesaj -->
<div class="alert alert-primary" role="alert">
<h4>Liste de vos simulations</h4>
</div>
<!-- simülasyon tablosu -->
<table class="table table-sm table-hover table-striped">
<!-- tablonun altı sütun başlığı -->
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Marié</th>
<th scope="col">Nombre d'enfants</th>
<th scope="col">Salaire annuel</th>
<th scope="col">Montant impôt</th>
<th scope="col">Surcôte</th>
<th scope="col">Décôte</th>
<th scope="col">Réduction</th>
<th scope="col">Taux</th>
<th scope="col"></th>
</tr>
</thead>
<!-- tablonun gövdesi (görüntülenen veriler) -->
<tbody>
<?php
$i = 0;
// simülasyon tablosunu tarayarak her bir simülasyon görüntülenir
foreach ($modèle->simulations as $simulation) {
// 6 sütunlu tablonun bir satırının görüntülenmesi - <tr> etiketi
// 1. sütun: satır başlığı (simülasyon numarası) - <th scope='row'> etiketi
// 2. sütun: parametre değeri [marié] - <td> etiketi
// 3. sütun: parametre değeri [enfants] - <td> etiketi
// 4. sütun: [salaire] parametre değeri - <td> etiketi
// 5. sütun: [impôt] parametre değeri (vergi) - <td> etiketi
// 6. sütun: [surcôte] parametre değeri - <td> etiketi
// 7. sütun: [décôte] parametre değeri - <td> etiketi
// 8. sütun: [réduction] parametre değeri - <td> etiketi
// 9. sütun: [taux] parametre değeri (vergi) - <td> etiketi
// 10. sütun: simülasyonu silme bağlantısı - <td> etiketi
print <<<EOT
<tr>
<th scope="row">$i</th>
<td>{$simulation["marié"]}</td>
<td>{$simulation["enfants"]}</td>
<td>{$simulation["salaire"]}</td>
<td>{$simulation["impôt"]}</td>
<td>{$simulation["surcôte"]}</td>
<td>{$simulation["décôte"]}</td>
<td>{$simulation["réduction"]}</td>
<td>{$simulation["taux"]}</td>
<td><a href="main.php?action=supprimer-simulation&numéro=$i">Supprimer</a></td>
</tr>
EOT;
$i++;
}
?>
</tr>
</tbody>
</table>
Yorumlar
- HTML tablosu, <table> etiketiyle oluşturulmuştur (6. ve 58. satırlar);
- tablonun sütun başlıkları <thead> etiketi içinde yer almaktadır (satır 8, 21). <tr> etiketi (table row, satır 9 ve 20) bir satırı sınırlar. Satır 10-15'te, <th> etiketi (table header) bir sütun başlığını tanımlar. Dolayısıyla on tane vardır. [scope="col"], başlığın sütuna uygulandığını gösterir. [scope="row"], başlığın satıra uygulandığını gösterir;
- 23-57. satırlar: <tbody> etiketi, tablo tarafından görüntülenen verileri çerçeveler;
- 40-51. satırlar: <tr> etiketi, tablonun bir satırını çerçeveler;
- 41. satır: <th scope=’row’> etiketi, satırın başlığını tanımlar;
- satır 42-50: her bir <td> etiketi, satırın bir sütununu tanımlar;
- 27. satır: Simülasyon listesi, bir ilişkisel tablo olan [$modèle→simulations] modelinde bulunur;
- satır 50: simülasyonu silmek için bir bağlantı. URL, tablonun 1. sütununda (satır 41) görüntülenen numarayı alır;
23.13.4.2. Görsel test
Bu farklı öğeleri [Tests] klasöründe bir araya getiriyoruz ve [vue-liste-simulations.php] görünümü için bir test şablonu oluşturuyoruz:

[vue-liste-simulations] görünümünün veri şablonu şu şekilde olacaktır:
<?php
// görünüm şablonu hesaplanır
$modèle = getModelForThisView();
function getModelForThisView(): object {
// sayfa verileri $modèle içine kapsüllenir
$modèle = new \stdClass();
// simülasyonlar sayfanın beklediği biçime getiriliyor
$modèle->simulations = [
[
"marié" => "oui",
"enfants" => 2,
"salaire" => 60000,
"impôt" => 448,
"décôte" => 100,
"réduction" => 20,
"surcôte" => 0,
"taux" => 0.14
],
[
"marié" => "non",
"enfants" => 2,
"salaire" => 200000,
"impôt" => 25600,
"décôte" => 0,
"réduction" => 0,
"surcôte" => 8400,
"taux" => 0.45
]
];
// menü seçenekleri
$modèle->optionsMenu = [
"Calcul de l'impôt" => "main.php?action=afficher-calcul-impot",
"Fin de session" => "main.php?action=fin-session"];
// başlık çubuğu resmi
$modèle->logo = "http://localhost/php7/scripts-web/impots/version-12/Tests/logo.jpg";
// şablonu oluşturur
return $modèle;
}
?>
<!-- HTML belgesi -->
<!doctype html>
<html lang="fr">
<head>
…
</head>
<body>
…
</body>
</html>
Açıklamalar
- 9-30. satırlar: HTML tablosu tarafından görüntülenen simülasyon tablosu;
- 32-34. satırlar: menü seçenekleri tablosu;
Bu görünümü görüntüleyelim:

Şu sonuç elde edilir:

Görsel olarak elde ettiğimiz sonuç bizi tatmin edene kadar bu görünüm üzerinde çalışırız. Ardından, yazmakta olduğumuz web uygulamasına bu görünümü entegre edebiliriz.
23.13.4.3. Görünüm modelinin hesaplanması

Görünümün görsel yapısı belirlendikten sonra, gerçek koşullarda görünüm modelinin hesaplanmasına geçilebilir. Bu görünüme yönlendiren durum kodlarını hatırlayalım. Bunlar yapılandırma dosyasında bulunur:
"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"
Dolayısıyla, simülasyon görünümünü görüntüleyen durum kodları [500, 600]'tir. Bu kodların anlamını bulmak için, jSON uygulaması üzerinde gerçekleştirilen [Postman] testlerinden yararlanabiliriz:
- [lister-simulations-500]: 500, başarılı bir [lister-simulations] eyleminin ardından ortaya çıkan durum kodudur; bu durumda kullanıcı tarafından gerçekleştirilen simülasyonların listesi görüntülenir;
- [supprimer-simulation-600]: 600, [supprimer-simulation] işleminin başarıyla tamamlanmasının ardından verilen durum kodudur. Bu silme işleminin ardından elde edilen yeni simülasyon listesi görüntülenir;
Artık simülasyon listesinin hangi anlarda görüntülenmesi gerektiğini bildiğimize göre, [vue-liste-simulations.php]'te bu listenin modelini hesaplayabiliriz:
<?php
// aşağıdaki değişkenler devralınır
// $request isteği: mevcut istek
// Oturum $session: uygulamanın oturumu
// dizi $config: uygulamanın yapılandırması
// dizi $content: denetleyicinin yanıtı
// hata yok
// dizi $content: denetleyicinin yanıtı
//
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
// görünüm şablonu hesaplanıyor
$modèle = getModelForThisView($request, $session, $config, $content);
function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
// sayfa verileri $modèle içinde kapsülleniyor
$modèle = new \stdClass();
// simülasyonlar sayfanın beklediği biçime getiriliyor
// bunlar, eylemi yürüten denetleyicinin yanıtında bulunur
// [Simulation] türündeki nesnelerden oluşan bir tablo biçiminde
$objetsSimulation = $content["réponse"];
// her bir [Simulation] nesnesi, ilişkisel bir diziye dönüştürülecektir
$modèle->simulations = [];
foreach ($objetsSimulation as $objetSimulation) {
$modèle->simulations[] = [
"marié" => $objetSimulation->getMarié(),
"enfants" => $objetSimulation->getEnfants(),
"salaire" => $objetSimulation->getSalaire(),
"impôt" => $objetSimulation->getImpôt(),
"surcôte" => $objetSimulation->getSurcôte(),
"décôte" => $objetSimulation->getdécôte(),
"réduction" => $objetSimulation->getRéduction(),
"taux" => $objetSimulation->getTaux()
];
}
// menü seçenekleri
$modèle->optionsMenu = [
"Calcul de l'impôt" => "main.php?action=afficher-calcul-impot",
"Fin de session" => "main.php?action=fin-session"];
// şablonu oluşturuyoruz
return $modèle;
}
?>
<!-- HTML belgesi -->
<!doctype html>
<html lang="fr">
<head>
…
</head>
<body>
…
</body>
</html>
Açıklamalar
- 26-36. satırlar: [v-liste-simulations.php] parçası tarafından kullanılan [$modèle→simulations] modelinin hesaplanması;
- 39-41. satırlar: [v-menu.php] parçası tarafından kullanılan [$modèle→optionsMenu] modelinin hesaplanması;
23.13.4.4. [Postman] Testleri
[lister-simulations-500] testi, 500 durum kodunu almamızı sağlıyor. Bu, simülasyonları görüntüleme isteğine karşılık geliyor:

[supprimer-simulation-600] testi, 600 durum kodunu almamızı sağlar. Bu, 0 numaralı simülasyonun başarıyla silinmesine karşılık gelir. Geri dönen sonuç, bir simülasyon eksik olan simülasyon listesidir:

23.13.5. Beklenmedik hataların görüntülenmesi
Burada, web uygulamasının normal kullanımı kapsamında meydana gelmemesi gereken bir hataya "beklenmedik hata" denir.
Örnek olarak, şu şekilde tanımlanan [Postman] [calculer-impot-3xx] testini ele alalım:

- [1-3]'te, POST isteği ve [calculer-impot] eylemi;
- [4-6]'e: burada POST'in üç parametresi için istediğimiz değeri tanımlayabiliriz:
- [4]: [marié] parametresi eksik;
- [5-6]: [enfants, salaire] parametreleri mevcut ancak geçersiz;
- [9]'te bu üç hata, durum kodu 338 ile bildirilir;
Oysa web uygulamasındaki HTML formunda bu durum meydana gelemez:
- tüm parametreler mevcuttur;
- iki radyo düğmesinin [value] özniteliklerinden değer alan [marié] parametresi, mutlaka [oui] veya [non] değerlerinden birine sahiptir;
- güncel bir tarayıcıda, <input type=’number’ min=’0’ step=’1’ …> öznitelikleri sayesinde, çocuk sayısı ve maaş için yapılan girişler mutlaka >=0 olan tamsayılar olur;
Ancak, bir kullanıcının [Postman] değerini seçip yukarıdaki [calcul-impot-3xx] testini sunucumuza göndermesini engelleyen hiçbir şey yoktur. Web uygulamamızın bu isteğe doğru şekilde yanıt verebildiğini gördük. HTML uygulaması kapsamında meydana gelmemesi gereken bir hataya “beklenmedik hata” adını vereceğiz. Böyle bir hata meydana gelirse, muhtemelen birisi uygulamayı “hacklemeye” çalışıyordur. Eğitim amaçlı olarak, bu tür durumlar için bir hata sayfası görüntülemeye karar verdik. Gerçekte, müşteriye gönderilen son sayfayı yeniden görüntüleyebiliriz. Bunun için, gönderilen son HTML yanıtını oturumda kaydetmek yeterlidir. Beklenmedik bir hata durumunda, bu yanıtı geri göndeririz. Böylece, görüntülenen sayfa değişmediği için kullanıcı, sunucunun hatalarına yanıt vermediğini düşünür.
23.13.5.1. Görünümün Sunumu
Beklenmedik hataları gösteren görünüm şöyledir:

[vue-erreurs.php] betiği tarafından oluşturulan görünüm üç bölümden oluşur:
- 1: Üst başlık, daha önce tanıtılan [v-bandeau.php] parçası tarafından oluşturulur;
- 2: Beklenmedik hata veya hatalar;
- 3: [v-menu.php] parçası tarafından oluşturulan, üç bağlantı içeren bir menü;
Beklenmedik hataların görüntüsü, aşağıdaki [vue-erreurs.php] komut dosyası tarafından oluşturulur:

<?php
// görünüm şablonu hesaplanıyor
$modèle = getModelForThisView();
function getModelForThisView(): object {
// sayfa verilerini $modèle içinde kapsülleme
$modèle = new \stdClass();
…
// şablon döndürülür
return $modèle;
}
?>
<!-- HTML belgesi -->
<!doctype html>
<html lang="fr">
<head>
<!-- Gerekli meta etiketleri -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
<title>Application impots</title>
</head>
<body>
<div class="container">
<!-- 12 sütunlu başlık -->
<?php require "v-bandeau.php"; ?>
<!-- iki sütunlu satır -->
<div class="row">
<!-- 3 sütunlu menü-->
<div class="col-md-3">
<?php require "v-menu.php" ?>
</div>
<!-- hata listesi -->
<div class="col-md-9">
<?php
print <<<EOT
<div class="alert alert-danger" role="alert">
Les erreurs inattendues suivantes se sont produites :
<ul>$modèle->erreurs</ul>
</div>
EOT;
?>
</div>
</div>
</div>
</body>
</html>
Açıklamalar
- 27. satır: [1] uygulamasının başlık çubuğunun eklenmesi;
- 32. satır: [2] menüsünün eklenmesi. Bu menü, başlığın altında üç sütun halinde görüntülenecektir;
- satır 34-44: hata alanının dokuz sütun halinde görüntülenmesi;
- satır 37-44: Beklenmedik hataları görüntüleyen [print] işlemi;
- satır 38: Bu görüntüleme, pembe arka planlı bir Bootstrap çerçeve içinde yapılacaktır;
- satır 39: bir tanıtım metni;
- 40. satır: <ul> etiketi, bir madde işaretli listeyi çevrelemektedir. Bu madde işaretli liste, [$modèle->erreurs] şablonu tarafından sağlanmaktadır;
Bu görünümdeki iki parçaya daha önce değinmiştik:
23.13.5.2. Görsel test
Bu farklı öğeleri [Tests] klasöründe bir araya getiriyoruz ve [vue-erreurs.php] görünümü için bir test şablonu oluşturuyoruz:

[vue-erreurs.php] görünümünün veri modeli şu şekilde olacaktır:
<?php
// görünüm şablonu hesaplanıyor
$modèle = getModelForThisView();
function getModelForThisView(): object {
// sayfanın verileri $modèle dosyasına aktarılır
$modèle = new \stdClass();
// beklenmedik hataların tablosu
$erreurs = ["erreur1", "erreur2"];
// hataların listesi oluşturulur HTML
$modèle->erreurs = "";
foreach ($erreurs as $erreur) {
$modèle->erreurs .= "<li>$erreur</li>";
}
// menü seçenekleri
$modèle->optionsMenu = [
"Calcul de l'impôt" => "main.php?action=afficher-calcul-impot",
"Liste des simulations" => "main.php?action=lister-simulations",
"Fin de session" => "main.php?action=fin-session",];
// başlık resmini
$modèle->logo = "http://localhost/php7/scripts-web/impots/version-12/Tests/logo.jpg";
// şablon geri döndürülüyor
return $modèle;
}
?>
<!-- HTML belgesi -->
<!doctype html>
<html lang="fr">
<head>
…
</head>
<body>
…
</body>
</html>
Açıklamalar
- 9-15. satırlar: HTML hata listesinin oluşturulması;
- 17-20. satırlar: menü seçenekleri tablosu;
Bu görünümü görüntüleyelim:

Şu sonuç elde edilir:

Görsel olarak elde ettiğimiz sonuç bizi tatmin edene kadar bu görünüm üzerinde çalışırız. Ardından, yazmakta olduğumuz web uygulamasına bu görünümü entegre edebiliriz.
23.13.5.3. Görünüm modelinin hesaplanması

Görünümün görsel yapısı belirlendikten sonra, gerçek koşullarda görünüm modelinin hesaplanmasına geçilebilir. Bu görünüme yönlendiren durum kodlarını hatırlayalım. Bunlar yapılandırma dosyasında bulunur:
"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"
Dolayısıyla, [2-4] satırlarında yer almayan durum kodları, beklenmedik hataların görünümünü görüntülememize neden olur.
[vue-erreurs.php] görünüm modelinin hesaplama kodu şöyledir:
<?php
// aşağıdaki değişkenler devralınıyor
// $request isteği: mevcut istek
// Oturum $session: uygulamanın oturumu
// dizi $config: uygulamanın yapılandırması
// dizi $content: denetleyicinin yanıtı
//
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
// görünüm şablonu hesaplanıyor
$modèle = getModelForThisView($request, $session, $config, $content);
function getModelForThisView(Request $request, Session $session, array $config, array $content): object {
// sayfa verileri $modèle içinde kapsülleniyor
$modèle = new \stdClass();
// kontrolörün yanıtındaki hatalar alınır
$réponse = $content["réponse"];
if (!is_array($réponse)) {
// tek bir hata mesajı
$erreurs = [$réponse];
} else {
// birden fazla hata mesajı
$erreurs = $réponse;
}
// hataların listesi oluşturulur HTML
$modèle->erreurs = "";
foreach ($erreurs as $erreur) {
$modèle->erreurs .= "<li>$erreur</li>";
}
// menü seçenekleri
$modèle->optionsMenu = [
"Calcul de l'impôt" => "main.php?action=afficher-calcul-impot",
"Liste des simulations" => "main.php?action=lister-simulations",
"Fin de session" => "main.php?action=fin-session",];
// şablon döndürülüyor
return $modèle;
}
?>
<!-- HTML belgesi -->
<!doctype html>
<html lang="fr">
<head>
…
</head>
<body>
…
</body>
</html>
Açıklamalar
- satır 19-32: [vue-erreurs.php] görünümü tarafından kullanılan [$modèle→erreurs] modelinin hesaplanması;
- satır 34-37: [v-menu.php] parçası tarafından kullanılan [$modèle→optionsMenu] modelinin hesaplanması;
23.13.5.4. [Postman] Testleri
[calculer-impot-3xx] testi, beklenen bir durum kodu olmayan 338 durum kodunu almamızı sağlar. HTML yanıtı ise şu şekildedir:

23.13.6. Uygulama menüsündeki eylemlerin uygulanması
Burada menü eylemlerinin uygulanmasını ele alacağız. Karşılaştığımız bağlantıların anlamlarını hatırlayalım
Görünüm | Bağlantı | Hedef | Rol |
Vergi Hesaplama | [Liste des simulations] | [main.php?action=lister-simulations] | Simülasyon listesini iste |
[Fin de session] | [main.php?action=fin-session] | ||
Simülasyon listesi | [Calcul de l’impôt] | [main.php?action=afficher-calcul-impot] | Vergi hesaplama görünümünü göster |
[Fin de session] | [main.php?action=fin-session] | ||
Beklenmedik hatalar | [Calcul de l’impôt] | [main.php?action=afficher-calcul-impot] | Vergi hesaplama görünümünü göster |
[Liste des simulations] | [main.php?action=lister-simulations] | ||
[Fin de session] | [main.php?action=fin-session] |
Bir bağlantıya tıklanmasının, bağlantının hedefine doğru bir GET eylemi tetiklediğini hatırlatmak gerekir. [lister-simulations, fin-session] eylemleri, bir GET işlemiyle hayata geçirilmiştir; bu da bunları bağlantı hedefleri olarak belirlememize olanak tanır. Eylem bir POST ile gerçekleştirildiğinde, JavaScript ile birleştirilmedikçe bağlantı kullanımı artık mümkün değildir.
Yukarıdaki işlemlerden, [afficher-calcul-impot] işleminin henüz uygulanmadığı anlaşılmaktadır. Bu, iki görünüm arasında gezinme işlemidir: jSON veya XML sunucularının bunu uygulaması için hiçbir neden yoktur, çünkü bu sunucularda "görünüm" kavramı yoktur. Bu kavramı getiren HTML sunucusudur.
Bu nedenle [afficher-calcul-impot] eylemini uygulamamız gerekiyor. Bu, sunucu içinde bir eylemin uygulanma sürecini gözden geçirmemizi sağlayacaktır.
Öncelikle, yeni bir ikincil denetleyici eklememiz gerekiyor. Buna [AfficherCalculImpotController] adını vereceğiz:

Bu denetleyici, [config.json] yapılandırma dosyasına eklenmelidir:
{
"databaseFilename": "database.json",
"rootDirectory": "C:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-12",
"relativeDependencies": [
…
"/Controllers/InterfaceController.php",
"/Controllers/InitSessionController.php",
"/Controllers/ListerSimulationsController.php",
"/Controllers/AuthentifierUtilisateurController.php",
"/Controllers/CalculerImpotController.php",
"/Controllers/SupprimerSimulationController.php",
"/Controllers/FinSessionController.php",
"/Controllers/AfficherCalculImpotController.php"
],
"absoluteDependencies": [
"C:/myprograms/laragon-lite/www/vendor/autoload.php",
"C:/myprograms/laragon-lite/www/vendor/predis/predis/autoload.php"
],
…
"actions":
{
"init-session": "\\InitSessionController",
"authentifier-utilisateur": "\\AuthentifierUtilisateurController",
"calculer-impot": "\\CalculerImpotController",
"lister-simulations": "\\ListerSimulationsController",
"supprimer-simulation": "\\SupprimerSimulationController",
"fin-session": "\\FinSessionController",
"afficher-calcul-impot": "\\AfficherCalculImpotController"
},
…
"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"
}
- 15. satır: yeni denetleyici;
- 30. satır: yeni eylem ve denetleyicisi;
- 35. satır: yeni denetleyici 800 durum kodunu döndürecektir. Görünüm değişiminde hata oluşmamalıdır;
[AfficherCalculImpotController.php] denetleyicisi şu şekilde olacaktır:
<?php
namespace Application;
// Symfony bağımlılıkları
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Response;
class AfficherCalculImpotController 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 {
// görünüm değişikliği - sadece bir durum kodunun ayarlanması gerekiyor
return [Response::HTTP_OK, 800, ["réponse" => ""], []];
}
}
Yorumlar
- 10. satır: Diğer ikincil denetleyiciler gibi, yeni denetleyici de [InterfaceController] arayüzünü uygular;
- görünüm değişiklikleri uygulaması kolaydır: hedef görünüme ilişkin durum kodunu belirtmek yeterlidir; burada, yukarıda görüldüğü gibi 800 kodunu belirtmek yeterlidir;
23.13.7. Gerçek Koşullarda Testler
Kod yazıldı ve her eylem [Postman] ile test edildi. Geriye, gerçek koşullarda görünümler arasındaki geçişi test etmek kalıyor. HTML oturumunu başlatmanın bir yoluna ihtiyacımız var. Sunucuya [action=init-session&type=html] parametrelerini göndermemiz gerektiğini biliyoruz. Bunları tarayıcının adres çubuğuna yazmak zorunda kalmamak için, uygulamamıza [index.php] komut dosyasını ekleyeceğiz:

[index.php] komut dosyası şu şekilde olacaktır:
<?php
// [main.php] adresine [html] modunda yönlendirme
header('Location: main.php?action=init-session&type=html');
- 4. satır: [header], yanıtta bir HTTP başlığı ekleyen PHP işlevidir. HTTP başlığı, [Location: main.php?action=init-session&type=html] başlığı, istemci tarayıcısından [Location]'te belirtilen URL hedefine yönlendirilmesini ister. [index.php] komut dosyası, URL ve [http://localhost/php7/scripts-web/impots/version-12/index.php] ile birlikte istenir. İstemci tarayıcısı, [main.php?action=init-session&type=html]'e göre URL'e yönlendirme aldığında, URL adresini [http://localhost/php7/scripts-web/impots/version-12/main.php?action=init-session&type=html] adresine göre talep edecek ve HTML oturumu başlayacaktır;
Başlangıçtaki URL, [http://localhost/php7/scripts-web/impots/version-12/] olarak basitleştirilebilir. URL'te herhangi bir sayfa belirtilmezse, varsayılan olarak [index.html, index.php] sayfaları kullanılır. Bu durumda [index.php] komut dosyası kullanılacaktır;
Hadi başlayalım: Şimdi birkaç sayfa dizisini tanıtacağız.
Tarayıcımızda istek izlemeyi etkinleştiriyoruz (Firefox’ta F12) ve URL başlatma komutunu [https://localhost/php7/scripts-web/impots/version-12/]’e gönderiyoruz:

- [4] adresinde, sunucudan gelen ilk yanıt bir 302 yönlendirmesidir:
- [5]'te, URL [http://localhost/php7/scripts-web/impots/13/main.php?action=init-session&type=html] adresine yeni bir istek gönderilir;
302 yönlendirmesini daha yakından inceleyelim:

- [8] adresinde, HTTP ve [302] kodları bir yönlendirme kodudur: istemci tarayıcıya, istenen URL adresinin taşındığı bildirilir. Yeni URL, [9] olarak belirtilir. Tarayıcı, yeni bir GET isteği ile bu yönlendirmeyi takip edecektir:

- [12-13]'e, tarayıcı tarafından yapılan yeni istek;
Aldığımız formu dolduralım;

Öyleyse birkaç simülasyon yapalım:


Simülasyonların listesini isteyelim:

İlk simülasyonu silelim:

Oturumu sonlandıralım:

Okuyucunun başka testler yapması önerilir.
23.14. jSON web hizmeti istemcisi
23.14.1. İstemci / sunucu mimarisi

Şimdi, [B] web hizmetinin jSON [A] istemcisine odaklanacağız. [A] istemcisi, [B] web hizmeti gibi katmanlı bir yapıya sahiptir:

Bu mimari, aşağıdaki kod düzeninde yansıtılmaktadır:

Sınıfların çoğu daha önce ele alınmış ve açıklanmıştır:
bağlantı paragrafı. | |
bağlantı içeren paragraf. | |
paragraf bağlantısı. | |
paragraf bağlantısı. | |
paragraf bağlantısı. | |
paragraf bağlantısı. |
23.14.2. [dao] katmanı

23.14.2.1. Arayüz
[dao] katmanının arayüzü şu şekilde olacaktır: [InterfaceClientDao.php]:
<?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): Simulation;
// sonuçların kaydedilmesi
public function saveResults(string $resultsFilename, array $simulations): void;
// kimlik doğrulama
public function authentifierUtilisateur(String $user, string $password): void;
// simülasyon listesi
public function listerSimulations(): array;
// bir simülasyonu silme
public function supprimerSimulation(int $numéro): array;
// oturum açma
public function initSession(string $type = 'json'): void;
// oturum sonlandırma
public function finSession(): void;
}
Açıklamalar
- 9. satır: [getTaxPayersData] yöntemi, vergi mükelleflerinin verilerini içeren jSON dosyasını işlemek için kullanılır. Bu yöntem, daha önce açıklanan (bağlantı paragrafı) [TraitDao] özelliği tarafından uygulanmaktadır;
- 15. satır: [saveResults] yöntemi, birden fazla vergi hesaplamasının sonuçlarını bir jSON dosyasına kaydetmeyi sağlar. Burada da bu yöntem, daha önce açıklanan (bağlantı paragrafı) [TraitDao] özelliği ile uygulanmaktadır;
- 12, 18, 21, 27, 30. satırlar: Web hizmeti tarafından kabul edilen her bir eylem için bir yöntem oluşturulmuştur;
23.14.2.2. Uygulama
[InterfaceClientDao] arayüzü, aşağıdaki [ClientDao] sınıfı tarafından uygulanmaktadır:
<?php
namespace Application;
// bağımlılıklar
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Response\CurlResponse;
class ClientDao implements InterfaceClientDao {
// Bir Özelliğin kullanımı
use TraitDao;
// özellikler
private $urlServer;
private $sessionCookie;
private $verbose;
// oluşturucu
public function __construct(string $urlServer, bool $verbose = TRUE) {
$this->urlServer = $urlServer;
$this->verbose = $verbose;
}
…
}
Açıklamalar
- 18-21. satırlar: Oluşturucu iki parametre alır:
- URL [$urlServer] web hizmetinin jSON;
- [$verbose] adlı bir boole değeri; bu değer TRUE olduğunda, sınıfın sunucudan gelen yanıtları konsolda görüntülemesi gerektiğini belirtir;
- 14. satır: oturum çerezi. Bunun işlevi, istemcinin 09 sürümünde açıklanmıştır (bağlantı paragrafı);
- 11. satır: Sınıf, arayüzün iki yöntemini uygulayan [TraitDao] özelliğini kullanır:
- [getTaxPayersData(string $taxPayersFilename, string $errorsFilename): array];
- [function calculerImpot(string $marié, int $enfants, int $salaire): Simulation];
23.14.2.2.1. [initSession] yöntemi
[initSession] yöntemi şu şekilde uygulanır:
public function initSession(string $type = 'json'): void {
// bir istemci oluşturulur HTTP
$httpClient = HttpClient::create();
// sunucuya kimlik doğrulaması yapılmadan istekte bulunulur
$response = $httpClient->request('GET', $this->urlServer,
["query" => [
"action" => "init-session",
"type" => $type
],
"verify_peer" => false
]);
// yanıt alınır
$this->getResponse($response);
// oturum çerezi alınır
$headers = $response->getHeaders();
if (isset($headers["set-cookie"])) {
// oturum çerezi mi?
foreach ($headers["set-cookie"] as $cookie) {
$match = [];
$match = preg_match("/^PHPSESSID=(.+?);/", $cookie, $champs);
if ($match) {
$this->sessionCookie = "PHPSESSID=" . $champs[1];
}
}
}
}
[init-session] eylemi, web hizmetine gönderilen ilk eylem olması gerektiğinden, [initSession] yöntemi, [dao] katmanından çağrılacak ilk yöntem olacaktır.
Açıklamalar
- 1. satır: İstenen oturum türü parametre olarak geçirilir. Parametre belirtilmezse, bir jSON oturumu başlatılır;
- 5-11. satırlar: Web hizmetine bir GET isteği gönderilir;
- 7-8. satırlar: GET'in iki parametresi;
- 10. satır: Güvenli iletişim durumunda (https şeması), web hizmeti tarafından gönderilen güvenlik sertifikası doğrulanmayacaktır;
- 13. satır: [getResponse] yöntemi, sunucudan yanıtı alır. Yanıtı bir dizi biçiminde döndürür. Burada, yöntemin sonucu kullanılmamaktadır. [getResponse] yöntemi, web hizmetinin yanıtındaki HTTP kodu 200 OK'ten farklıysa bir istisna oluşturur;
- 14-25. satırlar: [initSession] yöntemi, [dao] katmanında yürütülen ilk yöntem olduğu için, sonraki yöntemlerin bunu web hizmetine geri gönderebilmesi amacıyla oturum çerezi alınır. Bu kod, 09 sürümünde zaten yorumlanmıştır;
23.14.2.2.2. [getResponse] yöntemi
[getResponse] yöntemi, web hizmetinin yanıtını işlemekle görevlidir:
private function getResponse(CurlResponse $response) {
// yanıt alınır
$json = $response->getContent(false);
// günlükler
if ($this->verbose) {
print "$json\n";
}
// yanıt durumunu alıyoruz
$statusCode = $response->getStatusCode();
// hata mı?
if ($statusCode !== 200) {
// bir hata var
throw new ExceptionImpots($json);
}
// yanıt veriliyor
$array = json_decode($json, true);
return $array["réponse"];
}
Açıklamalar
- 1. satır: Yöntem özeldir;
- 1. satır: Yöntemin parametresi, [Symfony\Component\HttpClient\Response\CurlResponse] türündeki web hizmeti yanıtıdır; bu, [HttpClient]'in [CurlClient] tarafından uygulandığı durumlarda Symfony yanıt türüdür, yani [curl] kütüphanesi tarafından;
- 3. satır: Sunucudan jSON yanıtı alınır. [false] parametresinin, sunucudan gelen HTTP yanıtının durumu [3xx, 4xx, 5xx] aralığında olduğunda Symfony'nin bir istisna atmasını önlemek için kullanıldığını hatırlatırız;
- 5-7. satırlar: [$verbose] modundaysak, sunucunun yanıtını konsolda görüntüleriz;
- 9-14. satırlar: Sunucunun HTTP yanıt durumu 200'den farklıysa, hata mesajı olarak sunucunun jSON yanıtını içeren bir istisna atılır;
- 16. satır: jSON dizesi bir diziye kod çözülür;
- 17. satır: Yararlı bilgiler [$array["réponse"]] içinde bulunur;
23.14.2.2.3. [authentifierUtilisateur] yöntemi
[authentifierUtilisateur] yöntemi şu şekildedir:
public function authentifierUtilisateur(string $user, string $password): void {
// bir istemci oluşturuluyor HTTP
$httpClient = HttpClient::create();
// sunucuya kimlik doğrulamalı istek gönderiliyor
$response = $httpClient->request('POST', $this->urlServer,
["query" => [
"action" => "authentifier-utilisateur"
],
"body" => [
"user" => $user,
"password" => $password
],
"verify_peer" => false,
"headers" => ["Cookie" => $this->sessionCookie]
]);
// yanıt alınır
$this->getResponse($response);
}
Açıklamalar
- 5. satır: İstemcinin isteği bir POST'tir;
- 6-8. satırlar: URL dosyasındaki parametreler;
- 9-12. satırlar: POST dosyasındaki parametreler;
- 14. satır: oturum çerezi;
- 17. satır: yanıt okunur. Bir hata olması durumunda (HTTP kodu 200'den farklıysa), [getResponse] yönteminin kendisi bir istisna oluşturur;
23.14.2.2.4. [calculerImpot] yöntemi
public function calculerImpot(string $marié, int $enfants, int $salaire): Simulation {
// bir istemci oluşturulur HTTP
$httpClient = HttpClient::create();
// sunucuya kimlik doğrulaması yapılmadan, ancak oturum çerezi kullanılarak istek gönderilir
$response = $httpClient->request('POST', $this->urlServer,
["query" => [
"action" => "calculer-impot"],
"body" => [
"marié" => $marié,
"enfants" => $enfants,
"salaire" => $salaire
],
"verify_peer" => false,
"headers" => ["Cookie" => $this->sessionCookie]
]);
// yanıt alınır
$array = $this->getResponse($response);
return (new Simulation())->setFromArrayOfAttributes($array);
}
Açıklamalar
- 6-7. satırlar: URL yönteminin tek parametresi;
- satır 8-12: POST'in üç parametresi (satır 5);
- satır 17: yanıt işlenir;
- satır 18: buraya gelindiğine göre, [getResponse] yöntemi bir istisna oluşturmamıştır. [getResponse] tarafından döndürülen dizi ile başlatılmış bir [Simulation] nesnesi döndürülür;
23.14.2.2.5. [listerSimulations] yöntemi
public function listerSimulations(): array {
// bir istemci oluşturulur: HTTP
$httpClient = HttpClient::create();
// sunucuya kimlik doğrulaması yapılmadan, ancak oturum çerezi kullanılarak istek gönderilir
$response = $httpClient->request('GET', $this->urlServer,
["query" => [
"action" => "lister-simulations"
],
"verify_peer" => false,
"headers" => ["Cookie" => $this->sessionCookie]
]);
// yanıt alınır
return $this->getSimulations($response);
}
Açıklamalar
- 5. satır: GET yöntemi;
- 6-8. satırlar: GET'in tek parametresi;
- 13. satır: Simülasyonların alınması, özel [getSimulations] yöntemine emanet edilmiştir;
23.14.2.2.6. [getSimulations] yöntemi
private function getSimulations(CurlResponse $response): array {
// yanıt alınır JSON
$array = $this->getResponse($response);
// bir ilişkisel nesne dizisi elde edilir
// bunu bir Simulation nesneleri dizisi haline getireceğiz
$simulations = [];
foreach ($array as $simulation) {
$simulations [] = (new Simulation())->setFromArrayOfAttributes($simulation);
}
// Simülasyon nesnelerinin listesini oluşturuyoruz
return $simulations;
}
Açıklamalar
- satır 3: yanıtta bulunan dizi alınır. Bu, dizilerden oluşan bir dizidir ve her bir dizi, bir [Simulation] nesnesinin tüm özniteliklerine sahiptir;
- 6. satır: Buraya gelindiğine göre, [getResponse] yöntemi bir istisna oluşturmamıştır;
- 6-9. satırlar: Yanıtı kullanarak bir [Simulation] nesnesi dizisi oluşturulur;
- 11. satır: Bu diziyi döndürüyoruz;
23.14.2.2.7. [SupprimerSimulation] yöntemi
public function supprimerSimulation(int $numéro): array {
// bir istemci oluşturuyoruz HTTP
$httpClient = HttpClient::create();
// kimlik doğrulaması yapmadan, ancak oturum çerezi ile sunucuya istek gönderiyoruz
$response = $httpClient->request('GET', $this->urlServer,
["query" => [
"action" => "supprimer-simulation",
"numéro" => $numéro
],
"verify_peer" => false,
"headers" => ["Cookie" => $this->sessionCookie]
]);
// yanıt alınır
return $this->getSimulations($response);
}
Açıklamalar
- 5. satır: GET sorgusu gerçekleştirilir;
- satır 6-9: URL'in iki parametresi;
- 14. satır: Bir silme işleminden sonra, sunucu yeni simülasyon tablosunu döndürür. Bu tablo döndürülür;
23.14.2.2.8. [finSession] yöntemi
Web hizmeti ile yapılan bir çalışma oturumu normalde [finSession] yönteminin çağrılmasıyla sona erer:
public function finSession(): void {
// bir istemci oluşturulur: HTTP
$httpClient = HttpClient::create();
// sunucuya kimlik doğrulaması yapılmadan, ancak oturum çerezi ile istek gönderilir
$response = $httpClient->request('GET', $this->urlServer,
["query" => [
"action" => "fin-session"
],
"verify_peer" => false,
"headers" => ["Cookie" => $this->sessionCookie]
]);
// yanıt alınır
$this->getResponse($response);
}
Açıklamalar
- 5. satır: GET isteği yapılır;
- 6-8. satırlar: URL yönteminin tek parametresi;
- 13. satır: yanıt okunur. Yanıtta yer alan HTTP kodu 200'den farklıysa bir istisna atılır;
23.14.3. [métier] katmanı

23.14.3.1. Arayüz
[métier] katmanının arayüzü şöyledir: [InterfaceClientMetier.php]:
<?php
// ad alanı
namespace Application;
interface InterfaceClientMetier {
// bir vergi mükellefinin vergilerinin hesaplanması
public function calculerImpot(string $marié, int $enfants, int $salaire): Simulation;
// toplu iş modunda vergi hesaplaması
public function executeBatchImpots(string $taxPayersFileName, string $resultsFilename, string $errorsFileName): void;
// kimlik doğrulama
public function authentifierUtilisateur(String $user, string $password): void;
// simülasyon listesi
public function listerSimulations(): array;
// sonuçların kaydedilmesi
public function saveResults(string $resultsFilename, array $simulations): void;
// bir simülasyonu silme
public function supprimerSimulation(int $numéro): array;
// oturum açma
public function initSession(string $type = 'json'): void;
// oturum sonu
public function finSession(): void;
}
Açıklamalar
- Sadece 12. satırdaki [executeBatchImpots] yöntemi, [métier] katmanına özgüdür. Diğer tüm yöntemler, bunları uygulayan [dao] katmanına aittir;
23.14.3.2. [ClientMetier] sınıfı
[métier] katmanını uygulayan sınıf şöyledir:
<?php
namespace Application;
class ClientMetier implements InterfaceClientMetier {
// özellik
private $clientDao;
// üretici
public function __construct(InterfaceClientDao $clientDao) {
$this->clientDao = $clientDao;
}
// vergi hesaplaması
public function calculerImpot(string $marié, int $enfants, int $salaire): Simulation {
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
$simulations = [];
// veriler işlenir
foreach ($taxPayersData as $taxPayerData) {
// vergi hesaplanıyor
$simulations [] = $this->calculerImpot(
$taxPayerData->getMarié(),
$taxPayerData->getEnfants(),
$taxPayerData->getSalaire());
}
// sonuçların kaydedilmesi
if ($resultsFileName !== NULL) {
$this->clientDao->saveResults($resultsFileName, $simulations);
}
}
public function authentifierUtilisateur(String $user, string $password): void {
$this->clientDao->authentifierUtilisateur($user, $password);
}
public function listerSimulations(): array {
return $this->clientDao->listerSimulations();
}
public function saveResults(string $resultsFilename, array $simulations): void {
$this->clientDao->saveResults($resultsFilename, $simulations);
}
public function supprimerSimulation(int $numéro): array {
return $this->clientDao->supprimerSimulation($numéro);
}
public function finSession(): void {
$this->clientDao->finSession();
}
public function initSession(string $type = 'json'): void {
$this->clientDao->initSession($type);
}
}
Açıklamalar
- 10-12. satırlar: [métier] katmanının oluşturulabilmesi için [dao] katmanına bir referans gereklidir;
- 20-38. satırlar: Yalnızca [executeBatchImpots] yöntemi, [métier] katmanına özeldir. Diğer yöntemlerin uygulaması, yapılacak işi [dao] katmanındaki aynı isimli yöntemlere devreder;
- 23. satır: vergi mükelleflerinin verilerini [TaxPayerData] türünde bir nesne dizisi içinde elde etmek için [dao] katmanına başvurulur;
- 25. satır: Hesaplanan çeşitli simülasyonlar, [$simulations] tablosunda birleştirilir;
- satır 27-33: [$taxPayersData] tablosundaki her bir vergi mükellefinin vergisi hesaplanır;
- satır 35-37: [$simulations] tablosunda elde edilen sonuçlar, jSON dosyasına kaydedilir;
Not: [métier] katmanı neredeyse hiçbir işlev görmez. Bu katmanı silip tüm işlemleri [dao] katmanında birleştirmek de mümkündür.
23.14.4. Ana komut dosyası

Ana komut dosyası, aşağıdaki [config.json] dosyası ile yapılandırılmıştır:
{
"taxPayersDataFileName": "Data/taxpayersdata.json",
"resultsFileName": "Data/results.json",
"errorsFileName": "Data/errors.json",
"rootDirectory": "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-12",
"dependencies": [
"/Entities/BaseEntity.php",
"/Entities/TaxPayerData.php",
"/Entities/Simulation.php",
"/Entities/ExceptionImpots.php",
"/Utilities/Utilitaires.php",
"/Model/InterfaceClientDao.php",
"/Model/TraitDao.php",
"/Model/ClientDao.php",
"/Model/InterfaceClientMetier.php",
"/Model/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-12/main.php"
}
Ana komut dosyası [main.php] şu şekildedir:
<?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.json");
// yapılandırma alınır
$config = \json_decode(file_get_contents(CONFIG_FILENAME), true);
// komut dosyası için gerekli bağımlılıkları dahil ediyoruz
$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ımlanması
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"]);
// katman oluşturma [métier]
$clientMetier = new ClientMetier($clientDao);
// toplu iş modunda vergi hesaplaması
try {
// oturumun başlatılması
$clientMetier->initSession('json');
// kimlik doğrulama
$clientMetier->authentifierUtilisateur($config["user"]["login"], $config["user"]["passwd"]);
// sonuçları kaydetmeden vergi hesaplama
$clientMetier->executeBatchImpots(TAXPAYERSDATA_FILENAME, NULL, ERRORS_FILENAME);
// simülasyon listesi
$clientMetier->listerSimulations();
// bir simülasyonun silinmesi
$simulations = $clientMetier->supprimerSimulation(1);
// sonuçların kaydedilmesi
$clientMetier->saveResults(RESULTS_FILENAME, $simulations);
// oturum sonlandırma
$clientMetier->finSession();
// kimlik doğrulaması yapılmadan işlem - sistemin çökmesi gerekir
$clientMetier->listerSimulations();
} catch (ExceptionImpots $ex) {
// hata mesajı görüntülenir
print "Une erreur s'est produite : " . $ex->getMessage() . "\n";
}
// son
print "Terminé\n";
exit();
Açıklamalar
- 12-16. satırlar: [config.json] yapılandırma dosyasının kullanılması;
- 18-26. satırlar: tüm bağımlılıkların yüklenmesi;
- 28-34. satırlar: sabitlerin ve takma adların tanımlanması;
- satır 36-39: [dao] ve [métier] katmanlarının oluşturulması;
- 44. satır: jSON oturumunun başlatılması;
- satır 46: sunucuya kimlik doğrulaması yapılır;
- 48. satır: bir dizi vergi mükellefinin vergisinin hesaplanması. Sonuçlar kaydedilmez (2. parametre NULL);
- satır 50: tüm bu hesaplamaların sonuçları istenir;
- satır 52: 1 numaralı simülasyon (listede 2. sırada olan) silinir;
- satır 54: kalan simülasyonlar kaydedilir;
- satır 56: oturum sonlandırılır. Bu, oturum çerezinin silindiği anlamına gelir;
- 58. satır: simülasyon listesi istenir. Oturum çerezi silindiği için kimlik doğrulama işlemi yeniden yapılmalıdır. Dolayısıyla, kimliğin doğrulanmadığını belirten bir istisna mesajı almalıyız;
[taxpayersdata.json] dosyası şu şekildedir:
[
{
"marié": "oui",
"enfants": 2,
"salaire": 55555
},
{
"marié": "ouix",
"enfants": "2x",
"salaire": "55555x"
},
{
"marié": "oui",
"enfants": "2",
"salaire": 50000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 50000
},
{
"marié": "non",
"enfants": 2,
"salaire": 100000
},
{
"marié": "non",
"enfants": 3,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 5,
"salaire": 100000
},
{
"marié": "non",
"enfants": 0,
"salaire": 100000
},
{
"marié": "oui",
"enfants": 2,
"salaire": 30000
},
{
"marié": "non",
"enfants": 0,
"salaire": 200000
},
{
"marié": "oui",
"enfants": 3,
"salaire": 20000
}
]
12 vergi mükellefi vardır ve bunlardan 1'i yanlıştır. Dolayısıyla toplamda 11 simülasyon vardır. Bunlardan biri silinecektir. Geriye 10 tane kalmalıdır.
Ana komut dosyası çalıştırıldıktan sonra, jSON ve [results.json] dosyaları şu şekildedir:
[
{
"marié": "oui",
"enfants": "2",
"salaire": "55555",
"impôt": 2814,
"surcôte": 0,
"décôte": 0,
"réduction": 0,
"taux": 0.14
},
{
"marié": "oui",
"enfants": "3",
"salaire": "50000",
"impôt": 0,
"surcôte": 0,
"décôte": 720,
"réduction": 0,
"taux": 0.14
},
{
"marié": "non",
"enfants": "2",
"salaire": "100000",
"impôt": 19884,
"surcôte": 4480,
"décôte": 0,
"réduction": 0,
"taux": 0.41
},
{
"marié": "non",
"enfants": "3",
"salaire": "100000",
"impôt": 16782,
"surcôte": 7176,
"décôte": 0,
"réduction": 0,
"taux": 0.41
},
{
"marié": "oui",
"enfants": "3",
"salaire": "100000",
"impôt": 9200,
"surcôte": 2180,
"décôte": 0,
"réduction": 0,
"taux": 0.3
},
{
"marié": "oui",
"enfants": "5",
"salaire": "100000",
"impôt": 4230,
"surcôte": 0,
"décôte": 0,
"réduction": 0,
"taux": 0.14
},
{
"marié": "non",
"enfants": "0",
"salaire": "100000",
"impôt": 22986,
"surcôte": 0,
"décôte": 0,
"réduction": 0,
"taux": 0.41
},
{
"marié": "oui",
"enfants": "2",
"salaire": "30000",
"impôt": 0,
"surcôte": 0,
"décôte": 0,
"réduction": 0,
"taux": 0
},
{
"marié": "non",
"enfants": "0",
"salaire": "200000",
"impôt": 64210,
"surcôte": 7498,
"décôte": 0,
"réduction": 0,
"taux": 0.45
},
{
"marié": "oui",
"enfants": "3",
"salaire": "20000",
"impôt": 0,
"surcôte": 0,
"décôte": 0,
"réduction": 0,
"taux": 0
}
]
Gerçekten de 10 simülasyon var.
jSON [errors.json] dosyasının içeriği şöyledir:
{
"numéro": 1,
"erreurs": [
{
"marié": "ouix"
},
{
"enfants": "2x"
},
{
"salaire": "55555x"
}
]
}
Konsol çıktıları şu şekildedir (ayrıntılı modda, sunucudan gelen jSON yanıtları konsolda görüntülenir):
{"action":"init-session","état":700,"réponse":"session démarrée avec type [json]"}
{"action":"authentifier-utilisateur","état":200,"réponse":"Authentification réussie [admin, admin]"}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"2","salaire":"55555","impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"2","salaire":"50000","impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"3","salaire":"50000","impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"non","enfants":"2","salaire":"100000","impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"non","enfants":"3","salaire":"100000","impôt":16782,"surcôte":7176,"décôte":0,"réduction":0,"taux":0.41}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"3","salaire":"100000","impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"5","salaire":"100000","impôt":4230,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"non","enfants":"0","salaire":"100000","impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"2","salaire":"30000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"non","enfants":"0","salaire":"200000","impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45}}
{"action":"calculer-impot","état":300,"réponse":{"marié":"oui","enfants":"3","salaire":"20000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}}
{"action":"lister-simulations","état":500,"réponse":[{"marié":"oui","enfants":"2","salaire":"55555","impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"oui","enfants":"2","salaire":"50000","impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"50000","impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"non","enfants":"2","salaire":"100000","impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"non","enfants":"3","salaire":"100000","impôt":16782,"surcôte":7176,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"100000","impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3,"arrayOfAttributes":null},{"marié":"oui","enfants":"5","salaire":"100000","impôt":4230,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"non","enfants":"0","salaire":"100000","impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"oui","enfants":"2","salaire":"30000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0,"arrayOfAttributes":null},{"marié":"non","enfants":"0","salaire":"200000","impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"20000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0,"arrayOfAttributes":null}]}
{"action":"supprimer-simulation","état":600,"réponse":[{"marié":"oui","enfants":"2","salaire":"55555","impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"50000","impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"non","enfants":"2","salaire":"100000","impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"non","enfants":"3","salaire":"100000","impôt":16782,"surcôte":7176,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"100000","impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3,"arrayOfAttributes":null},{"marié":"oui","enfants":"5","salaire":"100000","impôt":4230,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14,"arrayOfAttributes":null},{"marié":"non","enfants":"0","salaire":"100000","impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41,"arrayOfAttributes":null},{"marié":"oui","enfants":"2","salaire":"30000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0,"arrayOfAttributes":null},{"marié":"non","enfants":"0","salaire":"200000","impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45,"arrayOfAttributes":null},{"marié":"oui","enfants":"3","salaire":"20000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0,"arrayOfAttributes":null}]}
{"action":"fin-session","état":400,"réponse":"session supprimée"}
{"action":"lister-simulations","état":103,"réponse":["pas de session en cours. Commencer par action [init-session]"]}
Une erreur s'est produite : {"action":"lister-simulations","état":103,"réponse":["pas de session en cours. Commencer par action [init-session]"]}
Terminé
23.14.5. [Codeception] Testleri
Önceki istemcilerde olduğu gibi, sürüm 12 istemcisi de [Codeception] testlerine tabi tutulabilir:

İstemcinin [métier] katmanına ait test sınıfı kodu, önceki istemcilerin test sınıflarındakine benzerdir:
<?php
// fonksiyon parametrelerinin beyan edilen türlerine sıkı bir şekilde uyulur
declare (strict_types=1);
// ad alanı
namespace Application;
// sabitlerin tanımı
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-12");
// yapılandırma dosyasının yolu
define("CONFIG_FILENAME", ROOT . "/Data/config.json");
// yapılandırma alınır
$config = \json_decode(\file_get_contents(CONFIG_FILENAME), true);
// komut dosyası için gerekli bağımlılıkları dahil etme
$rootDirectory = $config["rootDirectory"];
foreach ($config["dependencies"] as $dependency) {
require "$rootDirectory$dependency";
}
// mutlak bağımlılıklar (üçüncü taraf kütüphaneler)
foreach ($config["absoluteDependencies"] as $dependency) {
require "$dependency";
}
// Symfony bağımlılıkları
use Symfony\Component\HttpClient\HttpClient;
// test sınıfı
class ClientDaoTest extends \Codeception\Test\Unit {
// DAO katmanı
private $clientDao;
public function __construct() {
parent::__construct();
// yapılandırmayı alıyoruz
$config = \json_decode(\file_get_contents(CONFIG_FILENAME), true);
// [dao] katmanının oluşturulması
$clientDao = new ClientDao($config["urlServer"]);
// katman oluşturma [métier]
$this->métier = new ClientMetier($clientDao);
// oturum başlatma
$this->métier->initSession("json");
// kimlik doğrulama
$this->métier->authentifierUtilisateur("admin", "admin");
}
// testler
public function test1() {
$simulation = $this->métier->calculerImpot("oui", 2, 55555);
$this->assertEqualsWithDelta(2815, $simulation->getImpôt(), 1);
$this->assertEqualsWithDelta(0, $simulation->getSurcôte(), 1);
$this->assertEqualsWithDelta(0, $simulation->getDécôte(), 1);
$this->assertEqualsWithDelta(0, $simulation->getRéduction(), 1);
$this->assertEquals(0.14, $simulation->getTaux());
}
public function test2() {
….
}
…
public function test11() {
…
}
}
Açıklamalar
- 34-46. satırlar: Test sınıfının oluşturucusunun her testten önce çalıştırıldığı hatırlatılmalıdır;
- 38-41. satırlar: [dao] ve [métier] katmanlarının oluşturulması;
- 42-45. satırlar: [test1…, test11] test yöntemleri, [calculerImpot] yöntemini test eder. Bunun mümkün olabilmesi için, önceden bir jSON oturumu başlatılmalı ve kimlik doğrulaması yapılmalıdır;
Test sonuçları şöyledir:

Yapılması gereken birçok başka test daha vardır:
- [dao] katmanındaki farklı yöntemleri test etmek;
- web sunucusu tarafından döndürülen durumları test etmek. Bu durumlar önemlidir, çünkü değerleri hangi HTML sayfasının görüntüleneceğini belirler;