Asenkron İşlemler
"Kontrolcü yöntemlerine parametre olarak enjekte edilen sınıfların örneklerine aynı zamanda görünmlerinden de erişebilirsiniz."
Bu kütüphane bekleme gerektiren işlemleri arkaplanda ( Commands/ ) çalıştırarak ön tarafta herhangi bir geçikmeye neden olmadan kodların işlenmesini sağlar. Özellikle e-posta gönderimi, döngüsel işlemler, big data
gibi geçikmeye neden olabilecek işlemler için kullanışlıdır. Çalışma mekanizması Commands/ dizini içerisinde geliştirilen sınıflara 'SınıfAdı:yöntemAdı' söz diziminde istek gönderilerek gerçekleşir. Bu istek gönderiminden sonra kod akışı kaldığı yerden devam ederken istek gönderilen tarafta kodlar işlemeye başlar. Bu kodların işlenmesinde geçen süre kullanıcı tarafına yansıtılmaz. Bu süreçte bir çalıştırma dosyası oluşturulur ve bu dosyanın içerisinde çalışan işleme ait veriler ile ön taraftan gönderilen veriler yer alır. Bu dosya işlem tamamlanıncaya kadar var olmaya devam eder. İşlem başarılı bir şekilde tamamlandıktan sonra oluşturulan bu dosya kendiliğinden silinir. Bu işlem dosyaları ön tanımlı olarak FILES_DIR
dizinine kaydedilir. Böyle bir dosyayı var etmenin amacı ön taraftan arka tarafa data veya parametre gönderebilmektir. Gönderilen datalar bir dosyaya yazılarak tutulduğundan scalar (ölçeklenebilir) olmak zorundadır. Herhangi bir şekilde callable ve resource türünden data göderimi mümkün değildir. Bu sınıfın kullanımını gerektiren bir kod yazılıyorsa mutlaka kod ağırlığı mümkün mertebe arka tarafta olmalıdır. Commands/ tarafında başlangıç kontrolcü ve dosyalarının devre dışı kaldığı unutulmamalıdır. Başlangıç dosyalarında yer alan ayarlar ve fonksiyonlar arka tarafta da kullanılmak isteniyorsa komut kütüphanelerine
require ile dahil edilebilir. Başlangıç kotrolcüleri ie new ile veya statik olarak tanımlanmışsa Initialize::main() gibi kullanılarak dahil edilebilir. Bunlar dışında arkaplanda ZN'ye ait tüm kütüphaneler sorunsuzca kullanılabilir.
# Kurulum
ZN dağıtımları için terminal kurulum komutu. Bu sınıf Console kütüphanesi ile beraber gelmektedir.
↓ composer require znframework/package-console
# Örnek Uygulamalar
# Yöntemler
# Dinleme Yöntemleri [8.16.0][2024-08-23]
Async::setSocketURI(string $URI) : this |
Async::socket() : exit |
Async::output(array $data) : int |
Async::listen(string $procId, int $millisecond= 1000) : script |
# SetProcDirectory [8.3.0][2023-06-15]
İşlem dosyaları ($procId) varsayılan olarak FILES_DIR sabitinin gösterdiği konuma ( Projects/Frontend/Resources/Files/ ) kaydedilir. Bunu değiştirmek için bu yöntemden yararlanılır. Bu yöntemin başlangıç kontrolcüsünde kullanılması önerilir. Tüm veri akışı işlem dosyaları üzerinden yürütüldüğünden bu dosyaların konumu önemlidir.
string $location |
Hata içeriği gösterilecek işlemin hata dosyası adı. |
return this |
public function main()
{
Async::setProcDirectory(STORAGE_DIR . 'procs/');
}
Yukarıda yer alan örneğe göre artık ilgili işlem dosyalarının yolu Projects/Frontend/Storage/Files/procs/ dizini olacaktır.
# Run [8.3.0][2023-06-15]
Commands/ klasörüne yazılmış komut kütüphanelerine çalıştırma isteği gönderir.
string $command |
Çalıştırılacak Commands/Command.php dosyası. |
array $data = [] |
İlgili komut kütüphanesine gönderilecek datalar. Gönderilecek datalar scalar|array türden olabilir. Gönderilen datalar bir dosya üzerine yazıldığından callable vb. türde verilerin gönderilebilmesi mümkün değildir. |
string $procId = '' |
[8.14.0][2024-08-14] İlgili işlem dosyasına isim verilebilir. Herhangi bir parametre belirtilmezse uniqid() yönteminin oluşturacağı bir değer dosya ismi olarak kullanılır. Bir komut kütüphanesinin birden fazla kullanıcı tarafından tetiklenme durumu varsa bu değerin varsayılan olarak bırakılması en doğru seçenektir. Ya da kullanıcı adı veya ID bilgisi de tercih edilebilir. |
return string |
İstek sonrası oluşan işlem dosyasının yolunu döndürür. Dönen veri Projects/Frontend/Resources/Files/64d20043cecbf dizimine benzer eşsiz bir dosya adıdır. |
Çalıştırılacak komut kütüphanesi Commands/ dizininde yer alan sınıflardır. Bu sınıflara ait yöntemlere atılan istek 'SınıfAdı:yöntemAdı' şeklinde olmalıdır. İstek oluşturulduktan sonra data gönderimi amacıyla bir işlem dosyası oluşur ve bu dosyanın adı sistem tarafından $procId adında bir parametreyle gönderilir. Bu $procId bilgisini gönderilen verilerin kaydedildiği işlem dosyasından veri almak için kullanacağız.
public function main()
{
$procId = Async::run('SendMail:run',
[
'email' => '[email protected]',
'title' => 'Example',
'content' => 'Example Content'
]);
}
Yukarıdaki örnekte Commands/SendMail.php sınıfı içerisinde yer alan run() yöntemine istek atmış olduk. Bu istekle beraber arkplanda ihtiyaç duyacağımız E-posta gönderimi için gereken 'email', 'title' ve 'content' isimli verileri array $data parametresi ile gönderdik.
string $procId = '' [8.14.0][2024-08-14]
Bu parametre ile oluşan process dosyasının adı değiştirilebilir. Bilinen bir isim kullanılması kodun devamında kullanım kolaylığı sağlar. Dikkat edilmesi gereken aynı komutun birden fazla kullanıcı tarafından tetiklenmesi ihtimali varsa kullanıcıya göre değişebilen isimlerin kullanılması gerekir.
public function main()
{
$procId = Async::run('SendMail:run',
[
'email' => '[email protected]',
'title' => 'Example',
'content' => 'Example Content'
], UNAME . '-mail');
}
Yukarıdaki kodda UNAME kullanıcı adını tutan örnek bir sabit olarak kullanılmıştır.
External/Commands/ konumuna istek atacaksanız komutun önüne External\ ön eki getirilir.
public function main()
{
$procId = Async::run('External\SendMail:run',
[
'email' => '[email protected]',
'title' => 'Example',
'content' => 'Example Content'
], UNAME . '-mail');
}
# Process [8.3.0][2023-06-15]
Bu yöntem, çalıştırılacak kodları barındıracak şekilde Commands/ komut kütüphanelerine ait yöntemlerin içine yazılarak kullanılır. Callable bir yapıya sahipttir ve kendi içerisinde çeşitli işlemler gerçekleşir. Bu nedenle bu yöntemi mutlaka kullanmaya özen gösteriniz.
string $procId |
Bu prametre komut kütüphanesinin yönteminin 1. parametresinde saklanır ve olduğu gibi kullanılır. O nedenle bu parametre ile ilgili herhangi bir başka işlem yapılmaz. |
callable $callable | Çalıştırılacak kodların yazılacağı parametre. |
bool $displayError = false |
[8.5.0][2023-08-08] Arkaplanda yürütme isteği sonrası oluşabilecek hataların {procId}-error formatında bir dosyaya yazılıp yazılmayacağı. Bu değerin true olarak ayarlanması oluşan hataların daha kolay farkedilmesi adına tavsiye edilir. |
# $procId o an yapılan isteğe ait verileri tutan işlem dosyasıdır.
# Bu veri sistem tarafından gönderilir.
public function run($procId)
{
Async::process($procId, function($data)
{
Email::to($data['email'])->send($data['title'], $data['content']);
});
}
Yukarıdaki örnekte bir önceki örnek kullanımda gösterilen Async::run() yöntemi ile gönderilen datalara nasıl erişildiği ve kullanıldığı gösterilmiştir.
bool $displayError = false [8.5.0][2023-08-08]
Async::process() yönteminin 3. parametresinin true
olarak ayarlanması durumunda arkaplanda çalışan kod üzerinde oluşan denetlenebilir hataların procId-error dosyasına yazılmasını sağlar. Böylece hatanın neyden kaynaklandığının tespitinde kolaylık sağlanmış olur.
# Loop [8.14.0][2024-08-14]
Arkaplanda çalışacak işlerin bir döngü içerisinde sürekli çalışmaya devam etmesi istenebilir. Bu durumda while gibi kendi döngülerinizi kullanabileceğiniz gibi daha kısa bir şekilde döngü oluşturmanızı sağlayan bu yöntemi de kullanabilirsiniz.
int $count |
Döngünün kaç kez döneceği. Sürekli dönmesi isteniyorsa -1 gibi negatif bir değer kullanılabilir. |
int $waitSecond | Döngünün kaç saniye aralıklarla döneceğidir. Örnek: 5 olması durumunda her 5 saniyede bir döngü dönmeye devam edecektir |
callable $callable |
Çalıştırılacak kodların yazılacağı parametre. |
return void |
namespace Project\Commands;
use Async;
use Currency;
class Cron extends Command
{
public function updateCurrencies($procId)
{
Async::process($procId, function($data)
{
# Her saatte 1 kontrol et
Async::loop(-1, 3600, function()
{
# Para birimlerine ait değerleri güncelle.
Currency::update();
});
}, true);
}
}
Yukarıdaki örnekte her saatte bir para birimlerinin güncel değerlerini çeken kod yazmış olduk. int $count = -1 kullanımı sebebiyle döngü sürekli olarak devam edecektir.
# Close [8.14.0][2024-08-14]
Asenkron işlemlerde Commands/ tarafında işlemleri kapatmanız gerekmez. Bu işlem kendiliğinden gerçekleşir. Ancak döngü içeren veya uzun süreli çalışması gereken işlemlerin sonlandırılmasında kullanılabilir. Çünkü devam eden işlem bitmediğinden otomatik olarak kapatılamayacaktır. İşlemin kapatılmasıyla beraber işlem doyası da silinir.
string $procId = '' |
Async::run() ile oluşturulan ve durdurulmak istenen işlem dosyası. |
return string|false |
namespace Project\Controllers;
use Async;
class Cron extends Controller
{
public function startUpdateCurrencies()
{
Async::run('Cron:updateCurrencies', [], 'updateCurrencies');
}
public function stopUpdateCurrencies()
{
Async::close('updateCurrencies');
}
}
Yukarıdaki örnekte Async::run() ile tetiklenen 3. parametresi 'updateCurrencies' olarak belirlenmiş bir işlemin nasıl durdurulduğu gösterilmiştir.
# CloseAll [8.14.0][2024-08-14]
Devam eden işlemlerin tümünü sonlandırır.
return void |
Dosya: Controllers/Cron.php
namespace Project\Controllers;
use Async;
class Cron extends Controller
{
public function stopAll()
{
Async::closeAll();
}
}
# GetData [8.3.0][2023-06-15]
İşlem dosyasına yazılan verileri dizi formunda almak için kullanılır. Normalde Async::process() yöntemi içerisinde bu veriler $data değişkeni ile gelmektedir. Ancak bazı durumlarda devam eden işlemle ilgili verilerin alınmasında kullanılabilir.
string $procId = '' |
Verilerin alınacağı işlem dosyasının adı. |
return array |
public function main()
{
output(Async::getData('updateCurrencies'));
}
[
command => 'php zerocore Cron:updateCurrencies ...',
pid => 11732,
running => true,
signaled => false,
stopped => false,
exitcode => -1,
termsig => 0,
stopsig => 0,
run => 'Cron:updateCurrencies',
file => 'updateCurrencies',
path => 'assets/procs/UpdateCurrencies'
],
exampleKey1 => exampleValue1,
exampleKey2 => exampleValue2,
...
# IsExists [8.14.0][2024-08-14]
Devam eden işlem dosyasının fiziksel olarak var olup olmadığını kontrol eder.
string $procId = '' |
Async::run() ile oluşturulan işlem dosyası. |
return bool |
namespace Project\Controllers;
use Async;
class Cron extends Controller
{
public function main()
{
if( Async::isExists('updateCurrencies') )
{
# kodlar...
}
}
}
# IsRun [8.14.0][2024-08-14]
İşlem dosyasının işlenmeye devam edip etmediğini kontrol eder.
string $procId = '' |
Async::run() ile oluşturulan işlem dosyası. |
return bool |
namespace Project\Controllers;
use Async;
class Cron extends Controller
{
public function main()
{
if( Async::isRun('updateCurrencies') )
{
# kodlar...
}
}
}
# Remove [8.14.0][2024-08-14]
Async::close() yönteminden farklı olarak sadece işlem dosyasını fiziksel olarak silmek için kullanılır. Eğer dosya arkaplanda işlenmeye devam ediyorsa işlemi sonlandırmadan sadece işlem dosyasını silecektir.
string $procId = '' |
Async::run() ile oluşturulan işlem dosyası. |
return bool |
namespace Project\Controllers;
use Async;
class Cron extends Controller
{
public function main()
{
Async::remove('updateCurrencies');
}
}
# List [8.14.0][2024-08-14]
Devam eden işlemlerin tümünü sonlandırır.
return array |
Dosya: Controllers/Cron.php
namespace Project\Controllers;
use Async;
class Cron extends Controller
{
public function main()
{
output(Async::list());
}
}
[
status =>
[
command => 'php zerocore Cron:updateCurrencies',
pid => 7728,
running => true,
signaled => false,
stopped => false,
exitcode => -1,
termsig => 0,
stopsig => 0,
run => 'Cron:updateCurrencies',
file => 'UpdateCurrencies',
path => 'assets/procs/UpdateCurrencies'
]
]
# Report [8.3.0][2023-06-15]
Arkaplanda çalışan kodlarda gerçekleşen istisnai durumları raporlamak için kullanılır. Bu yöntemin kullanımı ile işlem dizinine $procId-report adında rapor dosyası oluşturulur.
array $data |
Rapor dosyasına yazdırılacak veriler. |
return int |
public function updateCurrencies($procId)
{
Async::process($procId, function($data)
{
if( ! Currency::update() )
{
Async::report(['error' => 'Kurlar güncellenemedi!']);
}
});
}
# Status [8.3.0][2023-06-15]
İşlem $procId değerlerine göre işlemi devam edenleri dizi türünde döndür. Yani fiziksel olarak bulunan işlem dosyalarını listeler.
string ...$procId |
Durumu kontrol edilecek işlem ID veya ID'ler. |
return array | Durumu devam eden işlemleri [$file1 => 1, $file2 => 1] biçiminde gösteren dizi döndürür. |
Dosya: Controllers/Cron.php
public function status()
{
$status = Async::status('sendMail', 'updateCurrencies');
output( $status )
}
$status işlemi devam edenleri listeyelen aşağıdaki gibi bir dizi üretir.
'updateCurrencies' => 1
]
Yukarıdaki çıktıda 'sendMail' işleminin tamamlandığı ancak 'updateCurrencies' işleminin devam ettiği anlaşılır.
# IsFinish [8.3.0][2023-06-15]
İşlem ID'lerine göre işlemlerin bitip bitmediğini döndürür. İşlemlerin hepsi bitmişse true en az biri bile bitmemişse false değeri döndürür.
string ...$procId |
Durumu kontrol edilecek işlem ID veya ID'ler. |
return bool | Durumu devam eden işlem varsa false yoksa true türünde değer döndürür. |
Dosya: Controllers/Cron.php
public function isFinish()
{
if( Async::isFinish('sendMail', 'updateCurrencies') )
{
# Belirtilen işlemlerin tümü biterse...
}
}
# GetProcId [8.3.0][2023-06-15]
Async::run() veya Async::process() yöntemlerinden sonra oluşan işlem dosyasınının yolunu verir.
return string |
Dosya: Controllers/Cron.php
namespace Project\Controllers;
use Async;
class Cron extends Controller
{
public function run()
{
Async::run('Cron:updateCurrencies', ['myData' => 5], 'updateCurrencies');
output( Async::getProcId() );
}
}
# DisplayError [8.6.0][2023-08-08]
Async::process() yönteminin 3. parametresi $displayError = true olarak kullanılan işlemlerde gerçekleşen hatalar '{procId}-error' görünümünde dosyalara yazılır. Bu yöntem, dosyalara yazılmış hataların ZN'nin standart hata görüntüleme aracında görüntülenmesini sağlar.
string $errorFile |
Hata içeriği gösterilecek işlemin hata dosyası adı. |
return string | Hata içeriği. |
Dosya: Controllers/DisplayError.php
public function main()
{
$errorFile = '64d20043cecbf-error';
echo Async::displayError($errorFile);
exit;
}
Yukarıda yer alan örnekteki gibi bir kontrolcü yardımı ile ilgili hata dosyasının görüntülenmesini sağlayabilirsiniz.
# Dinleyici Oluşturmak [8.16.0][2024-08-23]
Commands/ tarafında çalışan kodların işlem bittiğinde durumu hakkında bilgi almak için tasarlamış yöntemlerdir. Örneğin bir E-posta gönderimi veya SMS gönderiminde herhangi bir hata alınmasa dahi bu isteklerin gerçekleşip gerçekleşmediğinin kontrol edilmesi gerekir. İşte bu yöntemler bu süreci yönetmek ve kontrol etmek için geliştirilmişlerdir. Bu yöntemler AJAX ile gerçekleştirildiğinden gerçek bir soket işlemi yürütülmemektedir. Sadece soket işlemini simüle etmektedir.
Gereklilikler
● jQuery
# SetSocketURI [8.16.0][2024-08-23]
Commands/ tarafında gerçekleşen işlemlerin durumu hakkında bilgi almak için dinleme soketinin konumunu belirtir. Bu yöntem sadece 1 kez kullanılacağından Başlangıç Kontrolcüsünde (Initialize) kullanılmadır.
string $URI |
Async::socket() yönteminin yerleştirileceği URI bilgisi. |
return this |
public function main()
{
Async::setSocketURI('Home/socket');
}
Yukarıda yer alan örneğe göre Home/socket adresi arkaplan işlemlerinin durumunlarını dinlemek için kullanacağımız kontrolcü ve yöntemdir.
# Socket [8.16.0][2024-08-23]
Dinleme yapmak üzere soket oluşturur. Async::setSocketURI() yöntemi ile belirlenmiş URI'a bu yöntem yerleştirilir. Bu yöntem satır sonunda exit deyimi içerir.
return exit |
public function socket() : void
{
Async::socket();
}
Yukarıdaki örnekte görüldüğü gibi sadece Async::socket() yöntemi kullanılır ve farklı işlem yapılmaz. Bu yöntem exit içerdiğinden sonrasına kod yazılamaz.
# Output [8.16.0][2024-08-23]
Commands/ tarafından ön tarafa Async::listen() yönteminde kullanmak üzere veri döndürür.
array $data |
Döndürelecek veriler. Veriler scalar olmak zorundadır. |
return int |
public function run($procId)
{
Async::process($procId, function($data)
{
try
{
$status = Email::to($data['email'])->send($data['title'], $data['content']);
}
catch( Exception $e )
{
$error = $e->getMessage();
}
Async::output(['isEmail' => $status, 'email' => $data['email'], 'error' => $error]);
});
}
Yukarıdaki örnekte E-posta gönderiminin true veya false olan sonucunu döndürdük. Bu dizi ile dönen anahtarlar Async::listen() yöntemi ile konrol edilmek amacıyla kullanılır.
# Listen [8.16.0][2024-08-23]
Bu yöntem $procId bilgilerine göre dinleme yapar. Arkaplanda gerçekleşen işlemler sonucu dönen değerlere göre işlem yapar.
string $procId |
Dinlenilmek istenen işlem dosyası. Bu parametre doğrudan PHP tarafından değilde bir AJAX isteği sonrası gelen veri ile set edilecekse Async::listen({< parametre >}) gibi kullanımalıdır. |
int $millisecond = 1000 |
Kaç milisaniyede bir dinleme isteği gönderileceğidir. Varsayılan 1000 milisaniyedir yani 1 saniye. |
return this |
Parametrik Yöntemler
Async::success(callable $callback) : this |
Async::error(callable $callback) : this |
Öncelikle komut çalıştırma kodumuzu yazalım. Example adında örnek bir kontrolcümüzün ve send() yönteminde e-posta gönderimiyle ilgili komut dosyasına çalıştırma isteği göndereceğimiz kodlar olduğunu varsayalım.
public function send()
{
$procId = Async::run('SendEmail:run',
[
'email' => '[email protected]',
'title' => 'Example',
'content' => 'Example Content'
]);
View::sendEmailProcId( $procId );
}
Yukarıdaki örnekte SendMail:run komut dosyasına çalıştırma isteği gönderdik. Ve işlem dosyasını View::sendEmailProcId() yönteminde tuttuk. Bu veriyi Async::listen() ile dinleme yapmak için kullanacağız.
Şimdi de yukarıdaki kontrolcümüze ait örnek bir görünüm oluşturalım.
<span id="sendEmailStatus">E-posta gönderimi</span>
<script>
@Async::success
({<
// Async::output() ile döndürdüğümüz isEmail anahtarı.
if( data.isEmail )
{
$('#sendEmailStatus').html(data.email + ' adresine e-posta gönderildi.');
}
// işlem devam ederken sistem {status: processing} döndürür.
else if( data.status == 'processing' )
{
$('#sendEmailStatus').html('E-posta gönderiliyor...');
}
// Async::output() ile döndürdüğümüz error anahtarı.
else if( data.error )
{
$('#sendEmailStatus').html('E-posta gönderilemedi! Hata: ' + data.error);
}
>})::listen( View::sendEmailProcId() )
</script>
Yukarıdaki kod Async::setSocketURI() yöntemi ile belirlenmiş Home/socket adresini dinler. Daha sonra Async::output() ile oluşturulmuş dataya bakmaya başlar. Verinin bulunması halinde istek göndermeyi bırakır.
# Örnek SMS Gönderimi
Aşağıda örnek SMS gönderimi için aşamalar yer almaktadır.
public function main()
{
# Dinleme isteklerinin belirtilen konuma yapılmasını sağlar.
Async::setSocketURI('Initialize/socket');
}
public function socket() : void
{
# Dinleme isteklerine yanıt verir.
Async::socket();
}
<?php namespace Project\Commands;
use CURL;
use Async;
class SMS extends Command
{
public function send($procId)
{
Async::process($procId, function($data)
{
$error = false;
$url = 'http://g3.{provider}.com';
$content = '<MultiTextSMS>
<UserName>{userName}</UserName>
<PassWord>{password}</PassWord>
<Action>1</Action>
<Messages>
<Message>
<Mesgbody>' . $data['message'] . '</Mesgbody>
<Number>' . $data['phone'] . '</Number>
</Message>
</Messages>
<Originator>{originator}</Originator>
</MultiTextSMS>';
$send = CURL::init()
->option('url', $url)
->option('returntransfer', true)
->option('followlocation', true)
->option('postfields', $content)
->option('httpheader', ["Content-Type: text/plain"])
->exec();
if( CURL::errno() )
{
$error = CURL::errno();
}
$isSend = strstr($send, 'ID') ? true : false;
# Async::listen() yönteminde kullanılmak üzere sonuçları döndürüyoruz.
Async::output(['isSend' => $isSend, 'error' => $error]);
}, true);
}
}
<?php namespace Model\Home\Controllers;
use Async;
class Home
{
public static function main()
{
}
public static function ajaxSendSMS() : void
{
$procId = Async::run('SMS:send',
[
'phone' => '0555.......',
'message' => 'Bu bir test mesajıdır!'
]);
echo json_encode(['procId' => $procId]);
}
}
<span id="status"></span>
<a onclick="ajaxSendSMS()">Mesaj Gönder</a>
<script>
function ajaxSendSMS()
{
@ajax('Home/ajaxSendSMS')->dataType('json')->success
({<
@Async::success
({<
if( data.status == 'processing' )
{
$('#status').html('SMS gönderiliyor...');
}
else if( data.isSend )
{
$('#status').html('SMS gönderildi.');
}
else
{
$('#status').html('SMS gönderilemedi! Hata:' + data.error);
}
>})::listen({< data.procId >})
>})
}
</script>