Topik ini membahas dua fitur PHP 8.1 yang menyangkut arah eksekusi program. Sintaks first-class callable mengubah fungsi dan metode apa pun menjadi objek closure (fungsi yang dapat disimpan dalam variabel) dengan penulisan paling singkat. Fiber menyediakan eksekusi yang dapat dijeda dan dilanjutkan tanpa menghentikan alur utama. Contoh memakai pengurutan kata dan pekerjaan pengindeksan arsip sederhana. Setelah membacanya, fungsi dapat diperlakukan sebagai nilai secara alami, dan konsep pemrograman asinkron pada PHP dapat dipahami mulai dari fondasinya.
Sintaks first-class callable#
Sebelum PHP 8.1, callable dalam bentuk apa pun dibungkus dengan Closure::fromCallable(). PHP 8.1 menyediakan bentuk yang lebih pendek, yaitu menuliskan ... pada posisi daftar argumen, sehingga strlen(...) menghasilkan closure yang setara. Bentuk ini berlaku untuk fungsi global, metode milik objek melalui $objek->metode(...), dan metode statis melalui Kelas::metode(...).
<?php
declare(strict_types=1);
$panjang = strlen(...);
echo get_class($panjang), PHP_EOL;
echo $panjang('Arsip nasional'), PHP_EOL;
$nama = array_map(strtoupper(...), ['ali', 'budi', 'citra']);
print_r($nama);
class PembandingKata
{
public function urutkan(array $kata): array
{
usort($kata, $this->bandingkan(...));
return $kata;
}
private function bandingkan(string $a, string $b): int
{
return strlen($a) <=> strlen($b);
}
}
print_r((new PembandingKata())->urutkan(['ki', 'kira', 'kira-kira']));Closure
14
Array
(
[0] => ALI
[1] => BUDI
[2] => CITRA
)
Array
(
[0] => ki
[1] => kira
[2] => kira-kira
)Hasil strlen(...) adalah objek Closure sebagaimana tercetak pada baris pertama keluaran, dan objek itu dipanggil kembali dengan $panjang('Arsip nasional'). Sintaks strtoupper(...) menunjukkan bahwa closure siap pakai dapat dijadikan argumen langsung untuk array_map tanpa variabel perantara. Di dalam metode urutkan(), bentuk $this->bandingkan(...) menyelesaikan masalah pemanggilan metode privat sebagai callable, karena closure yang dihasilkan tetap terikat pada $this sehingga usort dapat memanggilnya.
Sintaks ini murni pembentuk objek, dan tanda ... di situ bukan variadic function (fungsi dengan daftar argumen panjang), melainkan penanda bahwa seluruh daftar argumen diserahkan kepada pemanggil closure.
Fiber, eksekusi yang dapat dijeda#
Fiber adalah blok eksekusi lengkap dengan tumpukan panggilannya sendiri yang dapat dijeda di tengah jalan dan dilanjutkan kemudian. Jeda dan lanjutan itu berlangsung secara cooperative (sukarela), yaitu fiber memutuskan sendiri kapan menyerahkan kendali melalui Fiber::suspend(), dan kode utama melanjutkannya dengan resume(). Program tetap berjalan pada satu alur tunggal, sehingga tidak ada dua kode yang dieksekusi pada saat bersamaan.
Diagram berikut memperlihatkan pergantian kendali antara kode utama dan fiber sepanjang satu siklus hidupnya.
Pergantian kendali inilah yang membuat fiber menjadi fondasi pustaka asinkron. Pada saat fiber menunggu sesuatu yang lambat, fiber menyerahkan kendali secara sukarela, dan pengatur jadwal milik pustaka menjalankan fiber lain. Tanpa fiber, pustaka semacam itu harus membagi pekerjaannya menjadi banyak fungsi kecil yang terhubung callback, yaitu fungsi yang dipanggil balik oleh pustaka, sehingga alurnya sulit dibaca.
Alur suspend dan resume#
Contoh berikut memuat satu fiber yang menjalankan tiga tahap pekerjaan pengindeksan dan berhenti dua kali untuk melapor. Method start() menjalankan fiber hingga Fiber::suspend() pertama, dan nilai yang diserahkan suspend() dikembalikan kepada pemanggil. Method resume() melanjutkan fiber, dan argumennya menjadi nilai kembalian dari Fiber::suspend() di dalam fiber.
<?php
declare(strict_types=1);
$fiber = new Fiber(function (): string {
echo 'Fiber: membaca daftar berkas', PHP_EOL;
$nilai = Fiber::suspend('berkas terbaca');
echo 'Fiber: melanjutkan dengan ', $nilai, PHP_EOL;
echo 'Fiber: menghitung ringkasan', PHP_EOL;
Fiber::suspend('ringkasan selesai');
echo 'Fiber: menutup pekerjaan', PHP_EOL;
return 'pekerjaan tuntas';
});
echo 'Utama: memulai fiber', PHP_EOL;
$catatan = $fiber->start();
echo 'Utama: fiber menyerah pada ', $catatan, PHP_EOL;
$catatan = $fiber->resume('data sensor');
echo 'Utama: fiber menyerah pada ', $catatan, PHP_EOL;
$fiber->resume();
echo 'Utama: hasil akhir ', $fiber->getReturn(), PHP_EOL;
echo 'Utama: fiber selesai? ', $fiber->isTerminated() ? 'ya' : 'belum', PHP_EOL;Utama: memulai fiber
Fiber: membaca daftar berkas
Utama: fiber menyerah pada berkas terbaca
Fiber: melanjutkan dengan data sensor
Fiber: menghitung ringkasan
Utama: fiber menyerah pada ringkasan selesai
Fiber: menutup pekerjaan
Utama: hasil akhir pekerjaan tuntas
Utama: fiber selesai? yaUrutan baris pada keluaran memperlihatkan pergantian giliran antara kode utama dan fiber. Nilai data sensor yang diberikan melalui resume() diterima $nilai pada titik suspend, sehingga jalur dua arah antara kedua pihak berlangsung tanpa variabel global. Setelah fiber mengembalikan nilainya dengan return, method getReturn() membaca hasil akhir tersebut dan isTerminated() menegaskan bahwa eksekusinya sudah selesai. Kelas Fiber juga menyediakan isStarted() dan isRunning() untuk membaca keadaan di antara tahap-tahap tersebut.
Exception yang dilempar di dalam fiber dan tidak ditangkap akan muncul pada pemanggil start() atau resume() yang menjalankannya, seolah-olah exception itu dilempar dari titik panggilan tersebut. Fiber yang berhenti karena exception dianggap berakhir, sehingga isTerminated() mengembalikan nilai true, sedangkan getReturn() tidak lagi menyediakan nilai kembalian dan melempar FiberError dengan keterangan bahwa fiber melempar exception. Perilaku ini menegaskan bahwa penanganan error tetap menjadi tanggung jawab kode utama di sekitar pemanggilan fiber.
Kesalahan umum#
Kesalahan pertama adalah memanggil Fiber::suspend() dari luar fiber. Tanpa fiber yang sedang berjalan, tidak ada eksekusi yang dapat dijeda, dan PHP melempar FiberError sebagaimana contoh berikut.
<?php
declare(strict_types=1);
try {
Fiber::suspend('di luar fiber');
} catch (FiberError $e) {
echo 'Error: ', $e->getMessage(), PHP_EOL;
}Error: Cannot suspend outside of a fiberKesalahan kedua adalah menganggap fiber seperti thread (alur eksekusi paralel milik sistem operasi). Fiber tidak menjalankan dua pekerjaan pada saat bersamaan dan tidak mempercepat perhitungan yang padat CPU, melainkan menata giliran eksekusi agar penantian yang lama tidak memblokir pekerjaan lain. Manfaatnya muncul pada pemrograman asinkron yang didukung pustaka I/O nonblokir, bukan pada pembagian beban hitung.
Kesalahan ketiga adalah memakai fiber tanpa pengatur jadwal. Fiber mentah menuntut pemanggil resume() yang eksplisit, sehingga menulis fiber sendiri untuk pekerjaan sekuensial biasa hanya menambah kompleksitas. Fiber berada di lapisan bawah, sedangkan pustaka asinkron di atasnya menyembunyikan pengaturan giliran dari kode aplikasi.
Ringkasan#
- Sintaks
fungsi(...)menghasilkan Closure dari fungsi, metode, maupun metode statis, dan menggantikanClosure::fromCallable(). - Fiber adalah eksekusi dengan tumpukan sendiri yang dapat dijeda lewat
Fiber::suspend()dan dilanjutkan lewatresume(). - Nilai mengalir dua arah, yaitu
suspend()menyerahkan nilai kepada pemanggil danresume()memberi nilai kepada fiber. - Fiber bukan thread, tidak mengeksekusi paralel, dan
Fiber::suspend()di luar fiber melempar FiberError.
Referensi#
- First class callable syntax — PHP Manual
— Dokumentasi resmi sintaks
...untuk membentuk closure. - Fibers — PHP Manual — Gambaran resmi konsep fiber dan semantik suspend serta resume.
- Fiber — PHP Manual — Dokumentasi kelas Fiber beserta method start, resume, dan getReturn.
- FiberError — PHP Manual — Dokumentasi error yang dilempar ketika fiber dipakai di luar aturannya.
- Fitur baru PHP 8.1 — PHP Manual — Daftar fitur PHP 8.1 yang memperkenalkan fibers dan first-class callable syntax.