Menyelami jantung Worker Mode: menulis skrip worker dengan frankenphp_handle_request() dan frankenphp_foreach_request(), perilaku superglobal antar request, batas jumlah request untuk mencegah memory leak, serta strategi restart dan penanganan crash worker di produksi.

Setelah di episode 4 kalian tahu cara mengaktifkan worker, episode ini membuka kotak hitamnya: apa yang sebenarnya terjadi di dalam skrip worker. Kita akan menulis worker manual dari nol dengan frankenphp_handle_request() dan frankenphp_foreach_request(), memahami bagaimana superglobal PHP di-reset antar request, dan belajar strategi menjaga worker tetap sehat di produksi — restart bertahap, batas request, dan penanganan crash.
Mengapa ini penting? Karena worker yang tidak dikelola dengan benar adalah sumber bug paling licin di dunia PHP: kode terlihat benar, berjalan di development, tetapi meledak di produksi karena state yang bocor antar request. Memahami mekanisme di baliknya membuat kalian bisa mendiagnosis dan mencegahnya.
Skrip worker adalah skrip PHP biasa yang dijalankan sekali oleh FrankenPHP. Setelah bootstrap selesai, skrip memasuki loop: setiap iterasi memanggil frankenphp_handle_request() yang memblokir hingga ada request masuk, lalu menjalankan callback handler untuk request tersebut.
<?php
// public/worker.php
require __DIR__ . '/vendor/autoload.php';
$app = new \App\Kernel();
$app->boot();
$handler = static function () use ($app) {
try {
echo $app->handle($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
} catch (\Throwable $exception) {
(new \MyExceptionHandler)->handleException($exception);
}
};
$maxRequests = (int) ($_SERVER['MAX_REQUESTS'] ?? 0);
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
$keepRunning = \frankenphp_handle_request($handler);
$app->terminate();
gc_collect_cycles();
if (!$keepRunning) {
break;
}
}
$app->shutdown();Mari bedah bagian pentingnya:
boot() dijalankan di luar loop, hanya sekali.$app yang sudah dibangun.frankenphp_handle_request($handler) — memblokir menunggu request, mengisi superglobal, memanggil handler, lalu kembali ke loop.$maxRequests — pembatas jumlah request sebelum worker restart sendiri (cegah memory leak).gc_collect_cycles() — memicu garbage collector di waktu yang terkendali, bukan di tengah pembuatan halaman.frankenphp_handle_request() vs frankenphp_foreach_request()Kedua fungsi ini adalah dua bentuk loop yang sama:
| Fungsi | Perilaku |
|---|---|
frankenphp_handle_request($handler) | Mengembalikan bool (false = berhenti); kalian mengatur loop sendiri |
frankenphp_foreach_request($handler) | Mengulang handler untuk setiap request sampai proses dihentikan |
frankenphp_handle_request() memberi kontrol paling besar — kalian bisa menyisipkan kode setelah response dikirim (misal $app->terminate(), logging, cleanup) seperti contoh di atas. Itu sebabnya ia menjadi pola yang direkomendasikan.
Ini adalah bagian yang paling sering disalahpahami. Di mode worker:
$_GET, $_POST, $_COOKIE, $_FILES, $_SERVER, $_REQUEST — di-reset otomatis sebelum setiap panggilan frankenphp_handle_request(). Nilainya berasal dari request yang sedang diproses.$_ENV — tidak di-reset antar request. Jika kode kalian memodifikasi $_ENV di dalam satu request, perubahannya akan terlihat oleh request berikutnya di thread yang sama.Untuk membaca nilai env worker (bukan env request), salin sebelum loop pertama:
<?php
$workerServer = $_SERVER; // $_SERVER worker, sebelum request pertama
$handler = static function () use ($workerServer) {
var_dump($_SERVER); // milik request saat ini
var_dump($workerServer); // milik worker script
};
while (\frankenphp_handle_request($handler)) {
// ...
}Aturan praktis: jangan menulis ke $_ENV di dalam handler, dan jangan pernah menyimpan data request ke variabel global/static tanpa sengaja.
Karena worker hidup terus, state berikut bertahan antar request:
static di dalam fungsi/metode.<?php
function getCounter(): int {
static $count = 0;
return ++$count; // 1, 2, 3, ... untuk setiap request!
}
while (\frankenphp_handle_request(static function () {
echo getCounter();
})) {
// ...
}getCounter() akan mengembalikan angka yang terus naik — perilaku yang tidak mungkin terjadi di mode classic. Framework modern (Symfony, Laravel Octane) menangani reset state untuk kalian, tetapi kode aplikasi sendiri tetap tanggung jawab kalian. Untuk Symfony, service yang menyimpan state per-request harus mengimplementasikan Symfony\Contracts\Service\ResetInterface agar di-reset otomatis oleh kernel.
Danger
Menggunakan framework yang sudah mendukung worker mode (Laravel Octane, Symfony 7.4+) adalah langkah teraman untuk produksi. Menulis worker manual di atas framework yang tidak dirancang untuk itu hampir pasti menghasilkan state leak yang sulit dilacak.
Banyak library PHP legacy masih membocorkan memori. Solusi pragmatis: restart worker setelah MAX_REQUESTS request.
{
frankenphp {
worker {
file /app/public/index.php
env MAX_REQUESTS 1000
}
}
}Di dalam skrip worker, nilai MAX_REQUESTS dibaca (lihat contoh pertama) untuk membatasi iterasi loop. Setelah mencapai batas, worker keluar dan FrankenPHP menyalakan worker baru yang bersih.
Worker bisa di-restart kapan pun tanpa mematikan server, asalkan admin API aktif:
curl -X POST http://localhost:2019/frankenphp/workers/restartIni berguna saat deploy: setelah file kode diperbarui, restart worker agar versi baru termuat — tanpa downtime.
Jika worker mati dengan exit code non-zero, FrankenPHP me-restart-nya dengan exponential backoff. Jika worker terus gagal dalam waktu singkat (misal typo di skrip), FrankenPHP akhirnya berhenti dengan error too many consecutive failures — ini fitur keamanan, bukan bug. Batasnya bisa disetel:
{
frankenphp {
worker {
file /app/public/index.php
max_consecutive_failures 10
}
}
}Untuk development, gunakan watch agar worker restart otomatis saat file berubah:
{
frankenphp {
worker {
file /app/public/index.php
watch /app/**/*.php
}
}
}Jika direktori watch tidak ditentukan, defaultnya ./**/*.{env,php,twig,yaml,yml} relatif terhadap direktori kerja.
Pada episode 5 ini, kalian telah menguasai mekanisme inti Worker Mode.
Inti yang harus dibawa pulang:
frankenphp_handle_request() untuk melayani banyak request.frankenphp_foreach_request() adalah bentuk loop otomatis; handleRequest memberi kontrol setelah-response.$_ENV tidak — jangan menulis ke sana.MAX_REQUESTS, restart via admin API, dan max_consecutive_failures.Di episode 6 selanjutnya kita menerapkan semua ini ke framework paling populer: Laravel dengan Octane — instalasi, octane:frankenphp, warm-up cache, dan konfigurasi Caddyfile produksi. Sampai jumpa di episode 6!