Setiap variabel yang menampung object memegang referensi kuat (strong reference): selama satu referensi saja masih hidup, object di belakangnya tidak akan pernah dihapus dari memori. Perilaku ini berubah menjadi masalah ketika struktur data berumur panjang — cache global, registry listener — ikut memegang object yang seharusnya sudah tidak dipakai. PHP membekali kita dua alat untuk mematahkan kebuntuan tersebut: class WeakReference untuk mengamati object, dan WeakMap untuk menyimpan data sampingan yang ikut lenyap bersama object kuncinya. Pada chapter ini kita pelajari keduanya beserta studi kasus event dispatcher yang bebas kebocoran memori.
Cara PHP Menghapus Object dari Memori #
Mesin PHP menghitung berapa variabel yang menunjuk ke sebuah object — mekanisme ini disebut reference counting. Ketika jumlah tersebut jatuh ke nol — variabel terakhir di-unset() atau keluar dari cakupannya — object langsung dihancurkan dan memorinya dikembalikan. Referensi melingkar (object A menunjuk B, object B menunjuk A) tidak pernah mencapai nol dengan cara ini; untuk kasus tersebut PHP menjalankan cycle collector tambahan secara berkala. Satu hal yang penting bagi kita: selama ada satu referensi kuat saja yang bertahan, object tetap hidup — siapa pun pemegangnya.
WeakReference: Mengamati Tanpa Mempertahankan #
Class WeakReference memegang object tanpa ikut menaikkan jumlah referensi kuatnya. Instance dibuat lewat metode statis WeakReference::create() — konstruktornya sengaja privat — lalu metode get() mengembalikan object aslinya, atau null bila object tersebut sudah dihancurkan:
<?php
class Pelanggan
{
public function __construct(public string $nama)
{
}
}
$budi = new Pelanggan('Budi');
$referensi = WeakReference::create($budi);
var_dump($referensi->get() === $budi);
echo $referensi->get()->nama . "\n";
$salinan = $budi;
unset($budi);
var_dump($referensi->get() === $salinan);
unset($salinan);
var_dump($referensi->get());Output yang Dihasilkan:
bool(true)
Budi
bool(true)
NULLMari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 9-10: object
Pelanggandibuat dan ditampung$budi;WeakReference::create($budi)membuat peninjau yang tidak menahan object. - Baris 12-13:
get()masih menemukan object yang sama (===menghasilkantrue) sehingga propertynamatetap bisa dibaca. - Baris 15-16 (
$salinan = $budi; unset($budi);): variabel$budihilang, tetapi$salinankini memegang referensi kuat — object belum boleh dihancurkan. - Baris 17:
get()tetap mengembalikan object yang hidup, dan identitasnya sama dengan$salinan. - Baris 19-20: setelah referensi kuat terakhir (
$salinan) lepas, object dihancurkan;get()tidak lagi menemukan apa pun dan mengembalikannull.
Weak reference tidak pernah menjaga object tetap hidup; weak reference hanya bertanya “masih ada atau tidak?”. Karena itu, kode yang memakai get() wajib siap menerima null kapan saja — itulah harga observasi yang tidak mengunci.
WeakMap: Cache yang Membereskan Diri Sendiri #
WeakMap adalah map yang kuncinya wajib object, dan setiap kunci disimpan secara lemah. Ketika object kunci dihancurkan, pasangan kunci-nilai di dalam map otomatis ikut lenyap tanpa pembersihan manual — perilaku yang menjadikannya wadah cache dan metadata yang ramah memori:
<?php
class Produk
{
public function __construct(public string $nama)
{
}
}
$cache = new WeakMap();
$laptop = new Produk('Laptop Kerja');
$ponsel = new Produk('Ponsel Pintar');
$cache[$laptop] = ['harga' => 7500000, 'stok' => 3];
$cache[$ponsel] = ['harga' => 3200000, 'stok' => 7];
echo 'Entri cache: ' . count($cache) . "\n";
echo 'Harga laptop: Rp' . $cache[$laptop]['harga'] . "\n";
unset($ponsel);
echo 'Entri setelah ponsel dihapus: ' . count($cache) . "\n";
var_dump(isset($cache[$laptop]));
var_dump(isset($cache[new Produk('Tablet Baru')]));
foreach ($cache as $produk => $info) {
echo $produk->nama . ' Rp' . $info['harga'] . "\n";
}Output yang Dihasilkan:
Entri cache: 2
Harga laptop: Rp7500000
Entri setelah ponsel dihapus: 1
bool(true)
bool(false)
Laptop Kerja Rp7500000Baca dan tulis dilakukan dengan sintaks array biasa — $cache[$laptop] — sehingga migrasi dari array asosiatif terasa mulus. Setelah unset($ponsel), count() turun dari 2 ke 1 dengan sendirinya: entri milik ponsel dibersihkan begitu object kuncinya dihancurkan. Pemeriksaan isset() menegaskan dua hal: object Produk yang persis sama (identitas, bukan kesamaan isi) dikenali sebagai kunci, sementara object baru yang berbeda tidak dikenal meski bertipe sama.
Studi Kasus: Event Dispatcher tanpa Kebocoran #
Bayangkan sistem notifikasi: object Langganan mendaftarkan diri ke dispatcher, lalu aplikasi berjalan berbulan-bulan. Jika dispatcher menyimpan listener di array biasa, setiap object yang pernah mendaftar tetap hidup selamanya meski pemilik aslinya sudah lama pergi — kebocoran memori klasik pada aplikasi berumur panjang. WeakMap membalik keadaan: dispatcher tetap bisa mengirim pesan kepada listener yang masih hidup, dan listener yang sudah mati hilang dari daftar dengan sendirinya:
<?php
class Langganan
{
public function __construct(public string $nama)
{
}
}
class EventDispatcher
{
private WeakMap $pendengar;
public function __construct()
{
$this->pendengar = new WeakMap();
}
public function daftar(object $pendengar, Closure $callback): void
{
$daftar = $this->pendengar[$pendengar] ?? [];
$daftar[] = $callback;
$this->pendengar[$pendengar] = $daftar;
}
public function kirim(string $pesan): void
{
foreach ($this->pendengar as $pendengar => $daftarCallback) {
foreach ($daftarCallback as $callback) {
$callback($pesan);
}
}
}
}
$notifikasi = new EventDispatcher();
$budi = new Langganan('Budi');
$siti = new Langganan('Siti');
$notifikasi->daftar($budi, function (string $pesan): void {
echo "Budi menerima: $pesan\n";
});
$notifikasi->daftar($siti, function (string $pesan): void {
echo "Siti menerima: $pesan\n";
});
$notifikasi->kirim('diskon 20% hari ini');
unset($siti);
$notifikasi->kirim('stok tinggal sedikit');Output yang Dihasilkan:
Budi menerima: diskon 20% hari ini
Siti menerima: diskon 20% hari ini
Budi menerima: stok tinggal sedikitMari kita bedah bagian-bagian pentingnya:
private WeakMap $pendengar;: registry listener disimpan dalamWeakMap, bukan array — object listener menjadi kunci, daftarClosuremenjadi nilai.$daftar = $this->pendengar[$pendengar] ?? [];: operator??aman karena operator tersebut memeriksa keberadaan kunci lebih dulu; mengakses$this->pendengar[$pendengar]secara langsung pada kunci yang absen akan melemparError.kirim(): dua perulangan bersarang — perulangan luar menjangkau setiap object pendengar yang masih hidup, perulangan dalam memanggil setiap callback miliknya.unset($siti);: karena$sitiadalah satu-satunya referensi kuat ke object tersebut, object langsung dihancurkan dan entrinya lenyap dari registry; pemanggilankirim()kedua hanya menjangkau Budi.
Perhatikan output pemanggilan kedua: tidak ada baris “Siti menerima” setelah unset($siti). Dengan array biasa, Siti akan terus menerima pesan — dan menahan memori — sampai akhir proses; detail yang jarang terasa pada skrip singkat, tetapi menentukan pada worker daemon yang berjalan lama.
Kapan Menggunakan Weak Reference #
Beberapa pola pemanfaatan paling umum:
- Cache per-object: hasil komputasi mahal yang melekat pada object tertentu — saat object mati, cache-nya ikut terhapus tanpa kebijakan kedaluwarsa tambahan.
- Registry observer/listener: daftar subscriber yang tidak boleh memaksa subscriber tetap hidup, seperti contoh
EventDispatcherdi atas. - Metadata runtime: menyimpan informasi tambahan pada object pihak ketiga tanpa menyentuh definisi class-nya.
- Pemetaan relasi: mencatat relasi object-ke-object yang otomatis bersih ketika salah satu sisi mati.
Alat ini bukan tanpa biaya. Setiap akses lewat get() bisa mengembalikan null, sehingga kode Anda wajib siap menghadapi ketiadaan object; pembaca kode awam juga perlu belajar dulu apa arti “weak”. Jika masa hidup object memang seumur aplikasi — konfigurasi, koneksi database tunggal — referensi kuat biasa justru lebih jujur dan lebih sederhana. Jadikan weak reference jawaban atas masalah masa hidup yang terukur, bukan gaya penulisan default.
Ringkasan #
WeakReference mengamati object tanpa mempertahankannya, sementara WeakMap menyimpan data sampingan yang ikut menguap bersama kuncinya. Keduanya melengkapi pemahaman kita tentang masa hidup object di PHP — dari reference counting sampai pola registry yang bebas kebocoran. Dengan seluruh peralatan object model — dari typed property sampai weak reference — kini saatnya menutup section ini dengan satu chapter rangkuman: praktik terbaik menyusun semuanya menjadi kode yang sehat.