Secara bawaan, fungsi PHP tidak punya ingatan: variabel lokal lahir saat barisnya dieksekusi dan lenyap begitu fungsi selesai, sehingga setiap pemanggilan selalu memulai hitungan dari nol. Padahal banyak kebutuhan nyata justru menuntut ingatan — nomor tiket yang terus bertambah, cache hasil perhitungan yang mahal, atau pencatat statistik yang diperbarui dari beberapa fungsi berbeda. PHP menjawab dengan dua kata kunci: global yang mengimpor variabel scope global ke dalam fungsi, dan static yang membuat variabel lokal bertahan hidup antar-pemanggilan. Chapter Variable Scope
telah memperkenalkan keduanya secara ringkas; pada Chapter ini kita membedah pola penggunaan nyatanya, jebakan yang menyertainya, dan kapan sebaiknya keduanya diganti alternatif yang lebih bersih.
Kata Kunci global untuk State Bersama #
Skenario paling umum kata kunci global adalah beberapa fungsi yang harus membaca dan menulis variabel yang sama. Setelah baris global $totalTerjual, $jumlahTransaksi; dijalankan, kedua nama tersebut di dalam fungsi menunjuk langsung ke variabel global — bukan salinan baru. Akibatnya, setiap penugasan di dalam fungsi mengubah variabel yang juga terlihat oleh fungsi lain yang mengimpor nama yang sama. Kode berikut menyimulasikan toko dengan dua kanal penjualan yang sama-sama memperbarui catatan penjualan:
<?php
$totalTerjual = 0;
$jumlahTransaksi = 0;
function catatPenjualanOffline(int $jumlah): void
{
global $totalTerjual, $jumlahTransaksi;
$totalTerjual += $jumlah;
$jumlahTransaksi++;
}
function catatPenjualanOnline(int $jumlah): void
{
global $totalTerjual, $jumlahTransaksi;
$totalTerjual += $jumlah;
$jumlahTransaksi++;
}
catatPenjualanOffline(150000);
catatPenjualanOnline(450000);
catatPenjualanOffline(75000);
echo "Total terjual: Rp$totalTerjual\n";
echo "Jumlah transaksi: $jumlahTransaksi\n";Output yang Dihasilkan:
Total terjual: Rp675000
Jumlah transaksi: 3Mari kita telaah baris-baris pentingnya:
- Dua variabel global dibuat di tingkat teratas sebagai buku catatan bersama seluruh skrip.
- Satu deklarasi
globalboleh mengimpor beberapa nama sekaligus — cukup pisahkan dengan koma. - Fungsi
catatPenjualanOffline()dancatatPenjualanOnline()sama-sama menambah$totalTerjualdan$jumlahTransaksi, sehingga urutan pemanggilan menentukan akumulasi nilai. - Setelah tiga transaksi (150000 + 450000 + 75000), total mencapai
675000dengan3transaksi tercatat.
Perhatikan pula apa yang membuat pola ini praktis sekaligus berisiko: kedua fungsi sepakat memakai nama yang sama tanpa pernah berkomunikasi lewat parameter. Perubahan isi $totalTerjual tidak tampak pada tanda tangan (signature) fungsi mana pun, sehingga pembaca kode harus memindai seluruh berkas untuk mengetahui siapa saja yang menulis variabel tersebut.
Mengakses Scope Global lewat $GLOBALS #
Jembatan kedua menuju scope global adalah array superglobal $GLOBALS. Array ini berisi seluruh variabel global, terindeks nama variabelnya tanpa tanda $, dan bisa dibaca serta ditulis dari scope mana pun tanpa deklarasi apa pun. Contoh berikut menulis ulang pencatat penjualan di atas dengan gaya $GLOBALS:
<?php
$totalTerjual = 0;
$jumlahTransaksi = 0;
function catatPenjualan(string $kanal, int $jumlah): void
{
$GLOBALS["totalTerjual"] += $jumlah;
$GLOBALS["jumlahTransaksi"]++;
}
catatPenjualan("offline", 150000);
catatPenjualan("online", 450000);
catatPenjualan("offline", 75000);
echo "Total terjual: Rp{$GLOBALS['totalTerjual']}\n";
echo "Jumlah transaksi: {$GLOBALS['jumlahTransaksi']}\n";Output yang Dihasilkan:
Total terjual: Rp675000
Jumlah transaksi: 3Indeks string "totalTerjual" merujuk variabel global $totalTerjual, sehingga operasi += di dalam fungsi mengubah nilai global secara langsung — hasilnya identik dengan versi global. Kedua baris echo terakhir juga menunjukkan bahwa $GLOBALS bisa dibaca langsung dari dalam string interpolation. Tabel berikut merangkum perbedaan praktis kedua jembatan:
| Aspek | global $x | $GLOBALS["x"] |
|---|---|---|
| Persiapan | Dideklarasikan di awal fungsi | Langsung dipakai di mana saja |
| Nama variabel dinamis | Tidak didukung | Didukung, mis. $GLOBALS[$nama] |
| Keterbacaan | Terpusat di blok atas fungsi | Tersebar di seluruh tubuh fungsi |
Dalam kode modern, $GLOBALS justru lebih jarang muncul daripada global; kehadirannya kebanyakan di kode lama atau kode framework tingkat rendah. Gunakan pengetahuan ini terutama untuk membaca kode yang ada, bukan sebagai resep menulis kode baru.
Variabel static untuk Ingatan Antar-Pemanggilan #
Kata kunci static menyelesaikan masalah yang berbeda: satu fungsi yang perlu mengingat sesuatu tentang dirinya sendiri. Variabel static tetap hidup di scope lokal setelah fungsi selesai, dan baris inisialisasinya hanya dijalankan sekali — tepat pada pemanggilan pertama. Pemanggilan berikutnya melanjutkan nilai terakhir alih-alih mulai dari nol. Pola klasiknya adalah pembangkit nomor urut tiket:
<?php
function buatNomorTiket(): string
{
static $nomor = 0;
return sprintf("TKT-%04d", ++$nomor);
}
echo buatNomorTiket() . "\n";
echo buatNomorTiket() . "\n";
echo buatNomorTiket() . "\n";Output yang Dihasilkan:
TKT-0001
TKT-0002
TKT-0003Mari kita bedah:
static $nomor = 0;hanya berdampak pada pemanggilan pertama; pada pemanggilan berikutnya baris ini dilewati dan nilai lama digunakan.- Pre-increment
++$nomormenaikkan nilai yang tersimpan, lalusprintf("TKT-%04d", ...)memformatnya menjadi empat digit dengan prefiksTKT-. - Tiga pemanggilan menghasilkan nomor berurutan tanpa satu pun variabel global — state terkunci rapi di dalam fungsi.
static sebagai Cache dengan Pola Memoization #
Pola paling bermanfaat dari static adalah cache, dan kombinasi terbaiknya muncul pada fungsi rekursif. Memoization adalah teknik menyimpan hasil yang sudah dihitung agar pemanggilan berikutnya cukup membaca cache. Fibonacci versi naive menghitung ulang nilai yang sama berkali-kali; versi bertenaga static menyimpan setiap hasil di cache yang dibagi oleh seluruh tingkat rekursi. Untuk membuktikan bedanya, kita ukur jumlah pemanggilan tiap versi memakai $GLOBALS sebagai alat hitung:
<?php
$statistik = ["lambat" => 0, "cepat" => 0];
function fibonacciLambat(int $n): int
{
$GLOBALS["statistik"]["lambat"]++;
return $n < 2 ? $n : fibonacciLambat($n - 1) + fibonacciLambat($n - 2);
}
function fibonacciCepat(int $n): int
{
$GLOBALS["statistik"]["cepat"]++;
static $cache = [0 => 0, 1 => 1];
if (!array_key_exists($n, $cache)) {
$cache[$n] = fibonacciCepat($n - 1) + fibonacciCepat($n - 2);
}
return $cache[$n];
}
echo "fibonacciLambat(15) = " . fibonacciLambat(15) . "\n";
echo "Jumlah pemanggilan naive: {$statistik['lambat']}\n";
echo "fibonacciCepat(15) = " . fibonacciCepat(15) . "\n";
echo "Jumlah pemanggilan memo : {$statistik['cepat']}\n";Output yang Dihasilkan:
fibonacciLambat(15) = 610
Jumlah pemanggilan naive: 1973
fibonacciCepat(15) = 610
Jumlah pemanggilan memo : 29Mari kita bedah peran tiap bagian:
$GLOBALS["statistik"]["lambat"]++dipakai sebagai penghitung instrumen — inilah salah satu sisa penggunaan$GLOBALSyang masih wajar: skrip pengukuran sekali jalan.fibonacciLambat()memanggil dirinya untuk setiap sub-masalah tanpa ingatan, sehingga menghitungfib(15)menular menjadi 1973 pemanggilan.static $cachedifibonacciCepat()diinisialisasi sekali lalu dibagi oleh seluruh pemanggilan rekursif; begitu sub-masalah sudah ada di cache, fungsi langsung mengembalikan nilainya.- Hasil kedua versi sama (
610), tetapi versi memo hanya membutuhkan 29 pemanggilan — sekitar 68 kali lebih sedikit.
Dua catatan penting dari pola ini. Pertama, cache static hidup selama skrip berjalan; untuk input berukuran besar, timbang jumlah memori yang dipakai. Kedua, contoh ini menunjukkan mengapa static akrab dengan rekursi: justru karena cache-nya dibagi lintas tingkat pemanggilan, memoization bekerja. Kita akan mendalami rekursi itu sendiri pada Chapter Recursion
.
Jebakan static yang Tidak Pernah Direset #
Sisi gelap static: variabelnya bertahan untuk semua pemanggilan — termasuk pemanggilan yang secara logis merupakan tugas berbeda. Perhatikan fungsi berikut yang seharusnya menerima satu alamat email lalu mengembalikan daftar penerima:
<?php
function tambahPenerima(string $email): array
{
static $penerima = [];
$penerima[] = $email;
return $penerima;
}
$untukBudi = tambahPenerima("[email protected]");
$untukSiti = tambahPenerima("[email protected]");
echo "Daftar untuk Siti: " . implode(", ", $untukSiti) . "\n";
echo "Daftar untuk Budi: " . implode(", ", $untukBudi) . "\n";Output yang Dihasilkan:
Daftar untuk Siti: [email protected], [email protected]
Daftar untuk Budi: [email protected]Pemanggilan kedua hanya menambahkan [email protected], tetapi daftar yang dikembalikan sudah berisi [email protected] — sisa dari tugas sebelumnya yang tidak pernah dibersihkan. Sementara itu $untukBudi tetap satu isi, karena array yang dikembalikan return adalah salinan, bukan tautan ke variabel static di dalam fungsi. Bug seperti ini sulit dilacak karena setiap pemanggilan tampak benar saat diuji sendiri-sendiri. Perbaikannya adalah menjadikan state sebagai parameter dengan nilai awal:
<?php
function tambahPenerima(string $email, array $penerima = []): array
{
$penerima[] = $email;
return $penerima;
}
$untukBudi = tambahPenerima("[email protected]");
$untukSiti = tambahPenerima("[email protected]");
echo "Daftar untuk Siti: " . implode(", ", $untukSiti) . "\n";
echo "Daftar untuk Budi: " . implode(", ", $untukBudi) . "\n";Output yang Dihasilkan:
Daftar untuk Siti: [email protected]
Daftar untuk Budi: [email protected]Versi baru membawa daftar masuk lewat parameter $penerima = [] dan mengembalikan salinan yang sudah diperbarui. Fungsi kembali murni — hasilnya hanya bergantung pada argument yang masuk — sehingga setiap pemanggilan berdiri sendiri. Aturan praktisnya: static cocok untuk state yang memang milik fungsi itu (counter, nomor urut, cache); begitu state mulai bocor antar-tugas, pindahkan ke parameter dan return value.
global sebagai Bau Desain dan Alternatifnya #
Mengapa komunitas PHP begitu ketat soal global? Karena setiap variabel global yang diimpor adalah dependensi tersembunyi: tidak tampak di tanda tangan fungsi, tidak bisa diuji tanpa menyiapkan variabel di luar, dan bisa berubah diam-diam oleh kode di bagian lain — fenomena action at a distance. Semakin besar aplikasi, semakin besar pula risiko dua fungsi saling menimpa variabel global yang sama. Bandingkan dua gaya berikut:
<?php
$diskonMember = 0.1;
function hitungHargaMemberGlobal(float $harga): float
{
global $diskonMember;
return $harga * (1 - $diskonMember);
}
function hitungHargaMember(float $harga, float $diskon): float
{
return $harga * (1 - $diskon);
}
echo hitungHargaMemberGlobal(200000) . "\n";
echo hitungHargaMember(200000, 0.1) . "\n";Output yang Dihasilkan:
180000
180000Kedua fungsi menghasilkan angka yang sama untuk masukan yang sama. Bedanya baru terasa saat kode tumbuh: hitungHargaMember() menyatakan kebutuhannya secara eksplisit ($diskon adalah parameter) sehingga mudah diuji — cukup panggil dengan nilai apa pun, tanpa persiapan apa pun di luar. Sementara hitungHargaMemberGlobal() menuntut variabel $diskonMember ada di scope global dengan nama persis itu; ubah nama variabel di luar dan fungsi rusak tanpa jejak error pada definisinya.
Tabel terakhir ini merangkum seluruh cara membawa state ke dalam fungsi yang telah dan akan kita pelajari:
| Teknik | State disimpan di | Cocok untuk |
|---|---|---|
| Parameter + return value | Pemanggil | Kebutuhan umum; fungsi murni dan mudah diuji |
Variabel static | Dalam fungsi | Counter, nomor urut, cache |
global / $GLOBALS | Scope global | Skrip kecil, kode lama, instrumen pengukuran |
Closure dengan use() | Dalam object Closure | Membawa konteks ke callback — dibahas pada Closures |
Ringkasan #
Kata kunci global dan static memberi fungsi akses ke state di luar siklus hidup lokalnya: global menembus dinding scope menuju variabel global, sedangkan static membuat variabel lokal bertahan antar-pemanggilan. static unggul untuk state yang memang milik fungsi — nomor tiket, cache memoization — sementara global paling baik diperlakukan sebagai warisan yang perlu dibaca, bukan ditiru; parameter dan return value hampir selalu lebih sehat. Dengan peta state yang lengkap, kita siap berkenalan dengan fungsi yang sama sekali tidak punya nama pada Chapter Anonymous Functions
.