Membuat worker PHP pertama dengan spiral/roadrunner-worker, memahami kelas Worker dan PSR7Worker, membangun loop request-response yang benar, dan menangani error agar worker tidak mati diam-diam di tengah request.

Setelah di episode 3 kita menyiapkan .rr.yaml yang benar — dengan server.command: "php app.php" — pada episode kali ini kita menulis file app.php itu sendiri: worker PHP yang menjadi jantung aplikasi long-running. Di sinilah PHP "berubah wujud" dari skrip per-request menjadi proses persistent yang melayani ribuan request.
Mengapa episode ini penting? Karena worker adalah tempat hampir semua bug RoadRunner terjadi: loop yang salah, error yang tidak tertangkap, response yang tidak dikirim — semuanya membuat worker mati atau request menggantung. Memahami pola worker yang benar adalah keterampilan dasar yang dipakai seluruh episode berikutnya.
spiral/roadrunner-workerWorker PHP dibangun di atas package Composer spiral/roadrunner-worker. Package ini menyediakan abstraksi untuk berkomunikasi dengan server Go melalui Goridge, termasuk implementasi PSR-7 untuk HTTP:
composer require spiral/roadrunner-worker spiral/roadrunner-httpCatatan: spiral/roadrunner-http membawa implementasi PSR-7 worker (kelas PSR7Worker), sedangkan spiral/roadrunner-worker menyediakan Worker inti yang dipakai semua jenis worker (HTTP, jobs, gRPC).
Worker dan PSR7WorkerAda dua lapisan yang perlu dipahami:
Spiral\RoadRunner\Worker — lapisan paling dasar. Meng-handle komunikasi biner dengan server Go (menerima payload, mengirim payload, menangani heartbeat). Dipakai langsung oleh worker jobs (episode 11) dan gRPC (episode 16).Spiral\RoadRunner\Http\PSR7Worker — lapisan di atas Worker yang menangani request HTTP sebagai objek PSR-7 (Psr\Http\Message\ServerRequestInterface dan ResponseInterface).Kalian menulis logika bisnis berhadapan dengan objek PSR-7 — bukan raw bytes — sehingga kode kalian portabel ke framework mana pun yang juga memakai PSR-7.
Buat file app.php di root proyek lab:
<?php
declare(strict_types=1);
use GuzzleHttp\Psr7\Response;
use Nyholm\Psr7\Factory\Psr17Factory;
use Spiral\RoadRunner\Http\PSR7Worker;
use Spiral\RoadRunner\Worker;
require __DIR__ . '/vendor/autoload.php';
$psr17 = new Psr17Factory();
$worker = Worker::create();
$psr7 = new PSR7Worker($worker, $psr17, $psr17, $psr17);
while ($request = $psr7->waitRequest()) {
try {
$body = sprintf('Halo dari worker %s!', getmypid());
$psr7->respond(new Response(200, ['Content-Type' => 'text/plain'], $body));
} catch (\Throwable $e) {
$psr7->getWorker()->error((string) $e);
}
}Penjelasan baris per baris:
Psr17Factory (untuk membuat request/response), Worker::create(), lalu membungkusnya dalam PSR7Worker.waitRequest() memblokir sampai request datang dari server Go. Inilah yang membuat worker persistent: loop tidak pernah berakhir.respond().error() — worker tidak mati diam-diam.Sekarang jalankan server:
./rr serve -c .rr.yamlServer mendengarkan di port 8080 dan meng-spawn 4 worker (sesuai config episode 3). Uji dengan curl:
curl http://localhost:8080/Output: Halo dari worker 12345! — perhatikan PID berbeda di tiap respons bila pool memilih worker yang berbeda. Untuk memastikan worker benar-benar persistent, bandingkan header respons: worker yang sama melayani request berurutan tanpa bootstrap ulang.
Tip
Untuk melihat berapa request yang dilayani satu worker sebelum direstart, atur pool.max_jobs kecil (misal 5) di .rr.yaml, lalu request berulang kali sambil mengamati PID berganti. Ini cara paling visual memahami siklus hidup worker dari episode 2.
Worker persistent mengubah cara menulis kode PHP. Tiga kesalahan paling umum:
| Kesalahan | Dampak | Solusi |
|---|---|---|
| Inisialisasi berat di dalam loop | Bootstrap ulang tiap request — boros | Bangun dependency di luar loop |
| Object/state yang bocor antar request | Data request sebelumnya ikut terbawa | Reset state per-request (episode 14) |
exit() / die() di dalam kode | Membunuh worker — request lain tersangkut | Lempar exception, biarkan di-catch loop |
Poin kunci: apa pun yang bisa dipindah ke luar loop, pindahkan. Koneksi database, koneksi Redis, cache opcode, dan container DI sebaiknya dibangun sekali saat worker lahir, lalu dipakai ulang untuk semua request. Di episode 6 kita lihat bagaimana framework menangani ini secara otomatis.
Jika worker menerima exception fatal yang tidak tertangkap, PHP akan berhenti dan server Go mendeteksi worker mati lalu meng-spawn pengganti — request yang sedang diproses gagal. Karena itu:
try/catch (seperti contoh di atas).$psr7->getWorker()->error($msg) supaya tercatat di log RoadRunner.Worker::stop() — pola ini berguna saat kalian harus flush buffer sebelum mati.Pada episode 4 ini, kalian telah membuat worker HTTP pertama yang benar-benar berjalan.
Inti yang harus dibawa pulang:
spiral/roadrunner-worker menyediakan Worker (biner) dan PSR7Worker (HTTP PSR-7).while ($request = $psr7->waitRequest()) adalah inti worker persistent.\Throwable dan kirim error ke server agar worker tidak mati diam-diam.exit(); biarkan supervisor yang mengelola siklus hidupnya.Di episode 5 selanjutnya, kita masuk lebih dalam ke HTTP plugin & pool management — mengatur http.address, middleware bawaan (gzip, static), dan parameter pool (num_workers, max_worker_memory, max_jobs) untuk aplikasi yang lebih realistis dengan static files dan dynamic routes. Sampai jumpa di episode 5!