20. Uygulama alıştırması – sürüm 10
Önceki sürümde, uygulamanın tüm kullanıcıları tarafından paylaşılan vergi verilerinin [Application] kapsamındaki bir bellekte depolanması gerektiği gösterilmişti. Bunu uygulamak için bir Redis sunucusu [https://redis.io] kullanacağız.
20.1. Redis
[Application] kapsamlı bellek, bir Redis sunucusu tarafından uygulanacaktır. Bu uygulama belleğine ihtiyaç duyan PHP komut dosyaları, bu sunucunun istemcileri olacaktır:

20.2. Redis Kurulumu
Laragon, varsayılan olarak etkinleştirilmemiş bir Redis sunucusuyla birlikte gelir. Bu nedenle, öncelikle bunu etkinleştirmek gerekir:

- [3]'te, [Redis] sunucusunu etkinleştirin;
- [4]'te, Redis istemcilerinin varsayılan olarak kullandığı [6379] bağlantı noktasını olduğu gibi bırakın;
Redis etkinleştirildikten sonra Laragon hizmetleri otomatik olarak yeniden başlatılır:

20.3. Komut modunda Redis istemcisi
Redis sunucusu komut modunda sorgulanabilir. Bir Laragon terminali açın (bkz. bağlantı paragrafı):

- [1]'te, [redis-cli] komutu, Redis sunucusunun komut modunda istemciyi başlatır;
Temmuz 2019 itibarıyla, Redis istemcisi sunucuyla iletişim kurmak için 172 komut kullanabilir [https://redis.io/commands#list]. Bunlardan biri olan [command count] [2], bu sayıyı [3] görüntüler.
Uygulamamızda ihtiyaç duyacağımız komutları PHP sunacağız. Redis'i tek bir amaç için kullanacağız: bir diziyi [‘attribut’=>’valeur’] Redis belleğinde depolamak. Bu işlem, [set attribut valeur] [4] Redis komutuyla gerçekleştirilir. Değer daha sonra [get attribut] [5] komutuyla geri alınabilir. İhtiyacımız olan tek şey budur.
Redis belleğini boşaltmak gerekebilir. Bu, [flushdb] [6] komutuyla yapılır. Ardından, [titre] [7] özniteliğinin değerini sorguladığımızda, özniteliğin bulunamadığını belirten [nil] [8] referansı elde edilir. Bir özniteliğin varlığını kontrol etmek için [exists] [9-10] komutunu da kullanabiliriz.
Redis istemcisinden çıkmak için [quit] [11] komutunu girin.
20.4. PHP için bir Redis istemcisi kurulumu
Şimdi PHP için bir Redis istemcisi kurmamız gerekiyor:

Redis istemcisini uygulayan çeşitli kütüphaneler mevcuttur. Biz [Predis] [https://github.com/nrk/predis] (Temmuz 2019) kütüphanesini kullanacağız. Bu kütüphane, öncekiler gibi [composer] ile birlikte bir Laragon terminalinde kurulur:

20.5. Sunucu kodu

[config-server.json] yapılandırma dosyası şu şekilde değişir:
{
"rootDirectory": "C:/myprograms/laragon-lite/www/php7/scripts-web/impots/version-10",
"databaseFilename": "Data/database.json",
"relativeDependencies": [
"/../version-08/Entities/BaseEntity.php",
"/../version-08/Entities/ExceptionImpots.php",
"/../version-08/Entities/TaxAdminData.php",
"/../version-08/Entities/Database.php",
"/../version-08/Dao/InterfaceServerDao.php",
"/../version-08/Dao/ServerDao.php",
"/../version-09/Dao/ServerDaoWithSession.php",
"/../version-08/Métier/InterfaceServerMetier.php",
"/../version-08/Métier/ServerMetier.php",
"/../version-09/Utilities/Logger.php",
"/../version-09/Utilities/SendAdminMail.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": "Data/logs.txt"
}
Yorumlar
- 5-15. satırlar: 10. sürüm, [impots-server.php] betiği dışında yeni bir şey getirmiyor. 08 ve 09 sürümlerinden öğeler kullanıyor;
- 19. satır: Az önce kurduğumuz [predis] kütüphanesine gerekli bir bağımlılık;
[impots-server.php] sunucu kodunda aşağıdaki değişiklikler yapılmıştır:
<?php
// fonksiyon parametrelerinin beyan edilen türlerine sıkı bir şekilde uyulması
declare (strict_types=1);
// ad alanı
namespace Application;
// PHP ile hata yönetimi
ini_set("display_errors", "0");
//
// yapılandırma dosyasının yolu
define("CONFIG_FILENAME", "Data/config-server.json");
// sınıf takma adı
use \Application\ServerDaoWithSession as ServerDaoWithRedis;
// oturum
$session = new Session();
$session->start();
…
…
// 1. günlük
$logger->write("\n---nouvelle requête\n");
// geçerli isteği alıyoruz
$request = Request::createFromGlobals();
// sadece ilk seferde kimlik doğrulama
if (!$session->has("user")) {
…
} else {
// günlük
$logger->write("Authentification prise en session…\n");
}
// geçerli bir kullanıcı var - alınan parametreler kontrol ediliyor
$erreurs = [];
// üç parametre olması gerekir GET
$method = strtolower($request->getMethod());
…
// hatalar mı var?
if ($erreurs) {
// müşteriye 400 hata kodu HTTP_BAD_REQUEST gönderiliyor
sendResponse($response, ["erreurs" => $erreurs], Response::HTTP_BAD_REQUEST, [], $logger);
// işlem tamamlandı
exit;
} else {
// günlükler
$logger->write("paramètres ['marié'=>$marié, 'enfants'=>$enfants, 'salaire'=>$salaire] valides\n");
}
// Çalışmak için gereken her şeye sahibiz
// 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ç sunucu hatası
doInternalServerError("[redis], " . utf8_encode($ex->getMessage()), $response, $config['adminMail'], $logger);
// tamamlandı
exit;
}
// [dao] katmanının oluşturulması
if (!$redis->get("taxAdminData")) {
// vergi verileri veritabanından alınır
$logger->write("données fiscales prises en base de données\n");
try {
// [dao] katmanının oluşturulması
$dao = new ServerDaoWithRedis($config["databaseFilename"], NULL);
// vergi verileri [application] kapsam belleğine aktarılıyor
// [TaxAdminData]->__toString yöntemi örtük olarak çağrılacaktır
$redis->set("taxAdminData", $dao->getTaxAdminData());
} catch (\RuntimeException $ex) {
// hata kaydedildi
doInternalServerError("[dao], " . utf8_encode($ex->getMessage()), $response, $config['adminMail'], $logger, $redis);
// işlem tamamlandı
exit;
}
} 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);
// günlükler
$logger->write("données fiscales prises dans redis\n");
}
// [métier] katmanının oluşturulması
$métier = new ServerMetier($dao);
// vergi hesaplaması
$result = $métier->calculerImpot($marié, (int) $enfants, (int) $salaire);
// cevap veriliyor
sendResponse($response, $result, Response::HTTP_OK, [], $logger, $redis);
// son
exit;
function doInternalServerError(string $message, Response $response, array $infos,
Logger $logger = NULL, \Predis\Client $predisClient = NULL) {
// $message: hata mesajı
// $response : yanıt HTTP
// $infos: e-posta gönderimi için bilgi tablosu
// $result: sonuç tablosu
// $logger: uygulamanın günlük kaydı
// $predisClient: bir istemci [predis]
//
// yöneticiye bir e-posta gönderilir
// SendAdminMail tüm istisnaları yakalar ve kendisi günlüğe kaydeder
$infos['message'] = $message;
$sendAdminMail = new SendAdminMail($infos, $logger);
$sendAdminMail->send();
// istemciye 500 hata kodu gönderilir
sendResponse($response, ["erreur" => $message], Response::HTTP_INTERNAL_SERVER_ERROR, [], $logger, $predisClient);
}
// HTTP yanıtını müşteriye gönderme işlevi
function sendResponse(Response $response, array $result, int $statusCode,
array $headers, Logger $logger = NULL, \Predis\Client $predisClient = NULL) {
// $response: HTTP yanıtı
// $result: sonuç tablosu
// $statusCode : yanıtın durumu HTTP
// $headers : yanıtta yer alması gereken başlıklar
// $logger: uygulamanın günlüğü
// $predisClient: bir istemci [predis]
//
// HTTTP durumu
$response->setStatusCode($statusCode);
// gövde
$body = \json_encode(["réponse" => $result], JSON_UNESCAPED_UNICODE);
$response->setContent($body);
// başlıklar
$response->headers->add($headers);
// gönderim
$response->send();
// günlük
if ($logger != NULL) {
$logger->write("$body\n");
$logger->close();
}
// bağlantının kapatılması [redis]
if ($predisClient != NULL) {
$predisClient->disconnect();
}
}
Açıklamalar
- 15. satır: Sunucu betiğinin uygulama değişikliğini yansıtmak için [\Application\ServerDaoWithSession] sınıfına [ServerDaoWithRedis] takma adı verilir;
- 18-19. satırlar: oturum korunur. Burada hatırlamamız gereken iki bilgi vardır:
- kullanıcının başarılı bir şekilde kimlik doğrulaması yapmış olması. Bu bilgi [session] kapsamındadır: belirli bir kullanıcıyla ilişkilidir ve diğer kullanıcılar için geçerli değildir;
- vergi idaresi verileri. Bu bilgi [application] kapsamındadır: belirli bir kullanıcıyla ilişkili değildir, ancak tüm kullanıcılar için geçerlidir;
- 54-64. satırlar: [redis] sunucusuyla iletişim kuracak olan [redis] istemcisinin oluşturulması. Bu istemci, sunucunun varsayılan bağlantı noktası üzerinden iletişim kuracaktır. Sunucu varsayılan bağlantı noktası üzerinden iletişim kurmuyorsa veya [localhost] makinesinde bulunmuyorsa, bu bilgilerin [\Predis\Client] sınıf oluşturucusuna aktarılması gerekir;
- 59. satır: Sunucunun yanıt verip vermediğini öğrenmek için istemciyi hemen sunucuya bağlarız;
- 60-65. satırlar: Redis sunucusuna bağlantı kurulamazsa, istemciye bir hata yanıtı gönderilir ve uygulama yöneticisine bir e-posta gönderilir;
- 67. satır: [redis] sunucusundan [taxAdminData] anahtarı istenir. Anahtar bulunamazsa, vergi verileri veritabanından alınır (72. satır);
- 75. satır: [taxAdminData] anahtarı, [TaxAdminData] türünde bir nesne olan [$taxAdminData] değişkeninin jSON dizesiyle ilişkili [redis] belleğine yerleştirilirQX türünde bir nesne olan [$taxAdminData] değişkeninin jSON dizesiyle ilişkili [taxAdminData] belleğine yerleştirilmiştir. [$redis→set] yöntemi, anahtar değeri olarak bir karakter dizisi bekler. Dolayısıyla, [TaxAdminData] türündeki nesneyi [string] türüne dönüştürmeye çalışacaktır. Bu durumda, dolaylı olarak [TaxAdminData->__toString] yöntemi çağrılacaktır. Bu yöntem, [TaxAdminData] nesnesinden jSON karakter dizisini üretir;
- 84. satır: [taxAdminData] anahtarı [redis] belleğinde bulunur, bu nedenle değeri alınır. Bunun, [TaxAdminData] nesnesinin jSON dizesi olduğu bilinmektedir. Bunu deşifre ederek bir öznitelik dizisi elde ediyoruz;
- 85. satır: Bu diziden yola çıkarak yeni bir [TaxAdminData] nesnesi oluşturulur;
- 87. satır: [dao] katmanı oluşturulur;
20.6. Müşteri kodu

Müşterinin 10. sürümü, 9. sürümle aynıdır. Yalnızca [config-client.json] yapılandırma dosyasında değişiklik vardır:
{
"rootDirectory": "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-10",
"taxPayersDataFileName": "Data/taxpayersdata.json",
"resultsFileName": "Data/results.json",
"errorsFileName": "Data/errors.json",
"dependencies": [
"/../version-08/Entities/BaseEntity.php",
"/../version-08/Entities/TaxPayerData.php",
"/../version-08/Entities/ExceptionImpots.php",
"/../version-08/Utilities/Utilitaires.php",
"/../version-08/Dao/InterfaceClientDao.php",
"/../version-08/Dao/TraitDao.php",
"/../version-09/Dao/ClientDao.php",
"/../version-08/Métier/InterfaceClientMetier.php",
"/../version-08/Métier/ClientMetier.php"
],
"absoluteDependencies": [
"C:/myprograms/laragon-lite/www/vendor/autoload.php"
],
"user": {
"login": "admin",
"passwd": "admin"
},
"urlServer": "https://localhost:443/php7/scripts-web/impots/version-10/impots-server.php"
}
Değişen tek şey, 24. satırda sunucunun URL değeridir.
Sonuçlar, 09 sürümündekilerle aynıdır. Şimdi yeni bir hata durumunu test edelim:

Konsoldaki sonuç şöyledir:
L'erreur suivante s'est produite : {"statut HTTP":500,"erreur":"[redis], Aucune connexion n’a pu être établie car l’ordinateur cible l’a expressément refusée. [tcp:\/\/127.0.0.1:6379]"}
Terminé
20.7. [Codeception] istemci testleri

Sürüm 10'daki [ClientMetierTest] test sınıfı, tek bir istisna dışında sürüm 09'dakiyle aynıdır:
<?php
// fonksiyon parametrelerinin beyan edilen türlerine sıkı uyum
declare (strict_types=1);
// ad alanı
namespace Application;
// sabitlerin tanımlanması
define("ROOT", "C:/Data/st-2019/dev/php7/poly/scripts-console/impots/version-10");
…
}
- 10. satır: test ortamı, sürüm 10'daki müşteri ortamıdır;
Testlere başlamadan önce, [redis-cli] istemcisini kullanarak [redis] sunucusunun belleğinden [taxAdminData] anahtarını silelim:

Şimdi testi çalıştıralım:

Şimdi sunucunun [logs.txt] günlüklerini inceleyelim:
05/07/19 08:52:16:396 :
---nouvelle requête
05/07/19 08:52:16:403 : Autentification en cours…
05/07/19 08:52:16:403 : Authentification réussie [admin, admin]
05/07/19 08:52:16:403 : paramètres ['marié'=>oui, 'enfants'=>2, 'salaire'=>55555] valides
05/07/19 08:52:16:407 : données fiscales prises en base de données
05/07/19 08:52:16:420 : {"réponse":{"impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}}
05/07/19 08:52:16:546 :
---nouvelle requête
05/07/19 08:52:16:555 : Autentification en cours…
05/07/19 08:52:16:555 : Authentification réussie [admin, admin]
05/07/19 08:52:16:556 : paramètres ['marié'=>oui, 'enfants'=>2, 'salaire'=>50000] valides
05/07/19 08:52:16:559 : données fiscales prises dans redis
05/07/19 08:52:16:559 : {"réponse":{"impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}}
05/07/19 08:52:16:668 :
---nouvelle requête
05/07/19 08:52:16:675 : Autentification en cours…
05/07/19 08:52:16:675 : Authentification réussie [admin, admin]
05/07/19 08:52:16:675 : paramètres ['marié'=>oui, 'enfants'=>3, 'salaire'=>50000] valides
05/07/19 08:52:16:678 : données fiscales prises dans redis
05/07/19 08:52:16:678 : {"réponse":{"impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}}
05/07/19 08:52:16:776 :
---nouvelle requête
…
Daha önce de belirtildiği gibi, her testte test sınıfının oluşturucusu yeniden çalıştırılır; bu nedenle, test edilen [ClientDao] sınıfı her testte var olmayan bir oturum çerezi ile örneklenir. Dolayısıyla, sanki 11 test, 11 farklı kullanıcıyı ve 11 farklı oturumu temsil ediyormuş gibi görünür.
- 6. satır: Vergi verileri veritabanından alınır;
- 13. ve 20. satırlar: Vergi verileri [redis] belleğinden alınır. Dolayısıyla burada, uygulamanın tüm kullanıcıları tarafından paylaşılan [application] kapsamlı bir bellek söz konusudur;
20.8. [Redis] sunucusunun web arayüzü
[Redis] sunucusunun komut modunda yönetilebileceğini görmüştük. Bu sunucu, bir web arayüzü aracılığıyla da yönetilebilir:

- [4]'te, yönetim sunucusu URL;
- [5], sunucu tarafından kaydedilen anahtarlar;
- [6], sunucunun mevcut durumu;
[5]'e tıklandığında, [taxAdminData] anahtarı hakkında bilgiler elde edilir:

- [7]'te, URL anahtarının bilgilerine erişim sağlayan [taxAdminData] ve [8];
- [9]'e, anahtarın durumu;
- [10]'te, anahtarın değeri: jSON dizesi, [TaxAdminData] türündeki bir nesneye ait olduğu anlaşılır;
- [11]'te anahtar silinebilir;
- [12]'te, başka bir anahtar eklenebilir;