Topik ini membahas properti readonly yang diperkenalkan pada PHP 8.1 beserta kelas readonly pada PHP 8.2. Pembahasan mencakup semantik tulis sekali, aturan inisialisasi, serta pola objek immutable yang dibangun di atas properti readonly. Objek immutable adalah objek yang isinya tidak berubah setelah dibuat. Contoh memakai catatan arsip dan koordinat sebagai value object, yaitu objek kecil yang dibedakan oleh nilainya. Setelah membacanya, properti yang hanya boleh diisi satu kali dapat ditegakkan oleh bahasa, bukan sekadar disepakati lewat komentar.
Properti readonly#
Properti readonly adalah properti bertipe yang nilainya hanya boleh ditulis satu kali, dan penulisan itu harus terjadi dari dalam lingkup kelasnya. Setelah terisi, penulisan ulang dari mana pun, termasuk dari dalam metode kelas itu sendiri, melempar Error. Pola pemakaiannya paling alami pada constructor property promotion, yaitu deklarasi properti langsung pada parameter konstruktor sebagaimana contoh berikut.
<?php
declare(strict_types=1);
class CatatanArsip
{
public function __construct(
public readonly string $nomor,
public readonly string $judul,
) {
}
}
$catatan = new CatatanArsip('ARS-2024-001', 'Laporan kegiatan bulanan');
echo $catatan->nomor, PHP_EOL;
echo $catatan->judul, PHP_EOL;
try {
$catatan->judul = 'Judul lain';
} catch (Error $e) {
echo 'Error: ', $e->getMessage(), PHP_EOL;
}ARS-2024-001
Laporan kegiatan bulanan
Error: Cannot modify readonly property CatatanArsip::$judulPembacaan properti readonly mengikuti aturan visibilitas biasa, sedangkan penulisannya hanya diperkenankan satu kali. Penjagaan ini bekerja pada tingkat properti, bukan tingkat kelas, sehingga satu objek boleh memiliki campuran properti biasa dan properti readonly. Pesan errornya menyebutkan properti yang bersangkutan, sehingga sumber pelanggaran langsung terlihat pada jejak eksekusi.
Aturan inisialisasi#
Properti readonly wajib memiliki deklarasi tipe, dan deklarasi tanpa tipe gagal pada saat kompilasi. Nilai bawaan tetap boleh dituliskan pada deklarasinya, dan nilai bawaan itu dihitung sebagai inisialisasi pertama. Inisialisasi dari luar lingkup kelas, misalnya $catatan->judul = 'x' pada objek yang propertinya belum terisi, juga ditolak karena penulisan pertama pun harus terjadi di dalam kelas.
// tidak-dijalankan
class Rusak
{
public readonly $isi; // fatal error: readonly wajib bertipe
}Properti static tidak boleh readonly, sehingga kata kunci readonly static selalu berakhir sebagai error kompilasi. Fungsi unset() pada properti readonly yang sudah terisi juga ditolak, karena menghapus nilai berarti mengubah keadaan objek. Properti yang dideklarasikan readonly tetapi belum pernah terisi dapat dibaca sebagai null sebagaimana properti bertipe lain yang belum diinisialisasi, dan status terisinya dapat diperiksa dengan isset().
Satu pelonggaran ditambahkan pada PHP 8.3 untuk mendukung penyalinan mendalam. Properti readonly yang sudah terisi boleh diinisialisasi ulang di dalam method __clone(). Dengan begitu, kelas yang propertinya berisi objek lain dapat membuat salinan mendalam yang isinya benar-benar baru, bukan salinan yang berbagi objek lama. Di luar konteks kloning, larangan tulis sekali tetap berlaku penuh tanpa kecuali.
Kelengkapan aturan ini menjadikan kontrak tulis sekali berlaku untuk seluruh jalur kode. Tidak ada pintasan melalui refleksi biasa maupun penugasan massal yang dapat melonggarkannya, dan pelanggaran apa pun tampil sebagai Error pada baris penulisannya sendiri.
Kelas readonly#
Kelas readonly, diperkenalkan pada PHP 8.2, menandai seluruh properti kelas sebagai readonly dengan satu kata kunci readonly sebelum class. Selain menghemat penulisan, kelas readonly melarang properti tanpa tipe dan melarang properti dinamis (properti yang dibuat di luar deklarasi kelas), sehingga bentuk objeknya benar-benar terkunci. Kelas readonly hanya boleh diwariskan oleh kelas readonly lain, agar penjagaan tidak longgar pada kelas turunannya.
<?php
declare(strict_types=1);
readonly class Koordinat
{
public function __construct(
public int $x,
public int $y,
) {
}
public function geser(int $dx, int $dy): static
{
return new static($this->x + $dx, $this->y + $dy);
}
}
$awal = new Koordinat(2, 3);
$tujuan = $awal->geser(5, -1);
echo "Awal: ({$awal->x}, {$awal->y})", PHP_EOL;
echo "Tujuan: ({$tujuan->x}, {$tujuan->y})", PHP_EOL;
echo "Awal tetap: ({$awal->x}, {$awal->y})", PHP_EOL;
try {
$tujuan->x = 99;
} catch (Error $e) {
echo 'Error: ', $e->getMessage(), PHP_EOL;
}Awal: (2, 3)
Tujuan: (7, 2)
Awal tetap: (2, 3)
Error: Cannot modify readonly property Koordinat::$xMetode geser() memperlihatkan pola khas objek immutable, yaitu perubahan dinyatakan sebagai objek baru, sedangkan objek lama tetap utuh. Pola ini dinamakan copy-on-write pada level objek, dan tipe kembalian static menjaga hasilnya tetap bertipe Koordinat. Baris ketiga keluaran di atas memperlihatkan bahwa pembacaan $awal setelah pemanggilan geser() tetap menunjukkan titik awal, bukan titik tujuan, sebab tidak ada penulisan ulang yang terjadi pada objek pertama.
Kesalahan umum#
Kesalahan pertama adalah menganggap readonly menjaga isi secara berlapis. Penjagaan hanya berlaku pada ikatan properti, bukan pada nilainya. Properti bertipe array tetap dapat diisi elemen baru melalui $catatan->tag[] = 'rutin', dan properti bertipe objek tetap memperkenankan objek di dalamnya berubah. Immutable yang benar menuntut seluruh properti bertipe skalar atau objek immutable lain.
Kesalahan kedua adalah mencoba menuliskan properti readonly untuk kedua kali dari dalam kelas, misalnya pada metode sinkronisasi. Larangan ini berlaku sama dari dalam maupun luar kelas, sehingga metode yang perlu mengubah nilai harus mengembalikan objek baru sebagaimana geser() pada contoh sebelumnya.
Kesalahan ketiga adalah memakai readonly pada properti static dengan harapan mendapat konstanta yang dapat dihitung. Kombinasi tersebut memang tidak disediakan bahasa, dan konstanta kelas yang nilainya dihitung cukup dituliskan sebagai ekspresi const biasa. Apabila nilai perlu dihitung sekali untuk semua objek, jalannya bukan inisialisasi pada konstruktor statis, melainkan konstanta kelas.
Ringkasan#
- Properti readonly hanya dapat ditulis satu kali, dari dalam lingkup kelasnya, dan wajib memiliki deklarasi tipe.
- Penulisan ulang setelah inisialisasi melempar Error, termasuk dari dalam metode kelasnya sendiri.
- Properti static tidak boleh readonly, dan
unset()pada properti yang sudah terisi ditolak. - Kelas readonly pada PHP 8.2 menandai seluruh propertinya readonly, melarang properti dinamis, dan hanya boleh diwariskan kelas readonly.
Referensi#
- Properties — PHP Manual — Dokumentasi properti kelas yang memuat bagian properti readonly.
- Fitur baru PHP 8.1 — PHP Manual — Daftar fitur PHP 8.1 yang memuat readonly properties.
- Fitur baru PHP 8.2 — PHP Manual — Daftar fitur PHP 8.2 yang memuat readonly classes.
- Konstruktor dan constructor property promotion — PHP Manual — Dokumentasi konstruktor beserta penulisan properti pada parameter konstruktor.