Banyak objek dalam aplikasi seharusnya tidak berubah setelah dibuat: data pengguna yang dikirim antar layer, nilai uang pada sebuah transaksi, atau konfigurasi yang dimuat sekali di awal request. Chapter sebelumnya sudah memperkenalkan readonly properties yang mengunci property satu per satu. Pendekatan tersebut ampuh, tetapi rawan lupa: satu property yang tidak ikut ditandai readonly menjadi celah mutasi diam-diam. PHP 8.2 menutup celah itu lewat readonly class — satu kata kunci yang membuat semua instance property otomatis readonly sekaligus melarang property dinamis menempel pada objek.
Mengunci Semua Property Sekaligus #
Kata kunci readonly diletakkan tepat sebelum class. Dengan penanda tunggal tersebut, PHP menerapkan tiga aturan sekaligus: semua instance property menjadi readonly, setiap property wajib bertipe, dan property tanpa deklarasi (dynamic property) ditolak keras. Bentuk paling umum menggabungkannya dengan constructor property promotion — parameter constructor yang diawali modifier public langsung menjadi property; fitur ini kita bedah tuntas pada chapter berikutnya.
<?php
readonly class Uang
{
public function __construct(
public int $jumlah,
public string $mataUang,
) {}
}
$harga = new Uang(150000, "IDR");
echo $harga->jumlah . " " . $harga->mataUang . "\n";Output yang Dihasilkan:
150000 IDRMari kita telaah kode di atas:
- Baris 3 (
readonly class Uang): kata kuncireadonlymembuat seluruh instance property di dalam class otomatis readonly — kita tidak perlu menulispublic readonlypada tiap property satu per satu. - Baris 5-8: kedua parameter constructor dipromosikan menjadi property
public int $jumlahdanpublic string $mataUangsekaligus terisi nilainya saat konstruktor berjalan. - Baris 11: objek
$hargadibuat. Setelah baris ini selesai, kedua property terkunci permanen. - Baris 12: pembacaan property tetap bebas dilakukan kapan saja —
readonlyhanya melarang penulisan ulang, bukan pembacaan.
Sama seperti readonly properties, property di readonly class tidak boleh diberi nilai bawaan (default value) karena nilai harus ditulis tepat sekali lewat proses inisialisasi di dalam class.
Apa yang Terjadi Saat Nilai Diubah #
Ada dua pintu untuk mengubah data objek: menulis ulang property yang sudah ada, atau menempelkan property baru. Readonly class menutup keduanya dengan Error fatal. Pertama, mari kita coba menulis ulang property yang sudah terisi:
<?php
readonly class Uang
{
public function __construct(
public int $jumlah,
public string $mataUang,
) {}
}
$harga = new Uang(150000, "IDR");
$harga->jumlah = 200000;Output yang Dihasilkan:
Fatal error: Uncaught Error: Cannot modify readonly property Uang::$jumlah in /home/z/my-project/php-course/content/php/advanced-object-model/readonly-classes/.verify_tmp.php:12
Stack trace:
#0 {main}
thrown in /home/z/my-project/php-course/content/php/advanced-object-model/readonly-classes/.verify_tmp.php on line 12Pesan Cannot modify readonly property Uang::$jumlah muncul pada baris penulisan — objek readonly menolak diubah setelah inisialisasi selesai. Sekarang pintu kedua, menempel property yang tidak pernah dideklarasikan:
<?php
readonly class Uang
{
public function __construct(
public int $jumlah,
public string $mataUang,
) {}
}
$harga = new Uang(150000, "IDR");
$harga->ket = "Diskon akhir pekan";Output yang Dihasilkan:
Fatal error: Uncaught Error: Cannot create dynamic property Uang::$ket in /home/z/my-project/php-course/content/php/advanced-object-model/readonly-classes/.verify_tmp.php:12
Stack trace:
#0 {main}
thrown in /home/z/my-project/php-course/content/php/advanced-object-model/readonly-classes/.verify_tmp.php on line 12Pada class biasa, property dinamis hanya memicu Deprecated sejak PHP 8.2. Pada readonly class, PHP langsung melempar Error (Cannot create dynamic property Uang::$ket) — tidak ada jalan menyelipkan state liar ke dalam objek yang sudah dikunci.
Pewarisan dan Aturan Tambahan #
Immutable tidak bisa diwariskan ke class yang mutable — otherwise class anak bisa membuka kunci milik orang tuanya. PHP menolak class biasa yang mencoba mewarisi readonly class:
<?php
readonly class TitikDasar
{
public function __construct(public int $x, public int $y) {}
}
class TitikLanjutan extends TitikDasar
{
public int $z;
}Output yang Dihasilkan:
Fatal error: Non-readonly class TitikLanjutan cannot extend readonly class TitikDasar in /home/z/my-project/php-course/content/php/advanced-object-model/readonly-classes/.verify_tmp.php on line 8Arah sebaliknya diperbolehkan: readonly class boleh mewarisi readonly class lain, dan seluruh aturan immutable tetap berlaku pada turunannya.
<?php
readonly class TitikDasar
{
public function __construct(public int $x, public int $y) {}
}
readonly class Titik3D extends TitikDasar
{
public function __construct(
int $x,
int $y,
public int $z,
) {
parent::__construct($x, $y);
}
}
$titik = new Titik3D(1, 2, 3);
echo "{$titik->x}, {$titik->y}, {$titik->z}\n";Output yang Dihasilkan:
1, 2, 3Pada class Titik3D, parameter $x dan $y sengaja tidak dipromosikan karena keduanya diteruskan ke parent::__construct() milik TitikDasar yang sudah mempromosikan property tersebut. Berikut rangkuman aksi yang ditolak beserta pesan error aslinya (semuanya kami uji pada PHP 8.4):
| Aksi yang ditolak | Pesan error PHP |
|---|---|
| Menulis nilai property | Cannot modify readonly property Uang::$jumlah |
| Menempel property dinamis | Cannot create dynamic property Uang::$ket |
| Mendeklarasikan property tanpa tipe | Readonly property Catatan::$isi must have type |
| Mendeklarasikan property statis | Static property Konfig::$versi cannot be readonly |
| Class biasa mewarisi readonly class | Non-readonly class TitikLanjutan cannot extend readonly class TitikDasar |
Aturan property statis perlu perhatian ekstra: PHP memang menolak property statis di readonly class, tetapi pesannya berbeda tergantung penulisan — deklarasi dengan nilai bawaan ditolak dengan pesan Readonly property Konfig::$versi cannot have default value, sedangkan tanpa nilai bawaan ditolak dengan Static property Konfig::$versi cannot be readonly. Kesimpulan praktisnya sama: jangan menaruh property statis di readonly class.
Pola DTO Immutable dengan Method with #
Readonly class paling sering dipakai untuk Data Transfer Object (DTO) — objek pembawa data murni antar layer aplikasi. Karena objek tidak bisa diubah, “perubahan” dilakukan dengan membuat objek baru lewat method berawalan with:
<?php
readonly class ItemPesanan
{
public function __construct(
public string $nama,
public int $jumlah,
public float $hargaSatuan,
) {}
public function withJumlah(int $jumlahBaru): self
{
return new self($this->nama, $jumlahBaru, $this->hargaSatuan);
}
public function total(): float
{
return $this->jumlah * $this->hargaSatuan;
}
}
$item = new ItemPesanan("Kopi Gayo", 2, 65000.0);
$itemRevisi = $item->withJumlah(3);
echo $item->jumlah . "\n";
echo $itemRevisi->jumlah . "\n";
echo $itemRevisi->total() . "\n";Output yang Dihasilkan:
2
3
195000Mari kita bedah pola ini:
- Baris 11-14 (
withJumlah): method sama sekali tidak menyentuh$this; method tersebut membangun instance baru lewatnew self(...)dengan nilai$jumlahBarusementara dua data lain disalin dari objek lama. - Baris 25-27: objek lama
$itemtetap utuh dengan jumlah2, sedangkan$itemRevisiadalah objek berbeda berisi jumlah3dan total195000. Kedua objek hidup berdampingan tanpa saling merusak.
Pola ini membuat riwayat perubahan mudah ditelusuri: setiap revisi menghasilkan objek baru, jadi kode lain yang masih memegang objek lama tidak pernah terkejut oleh perubahan mendadak.
Ringkasan #
readonly class memindahkan tanggung jawab “jangan lupa menandai tiap property” menjadi satu keputusan di level class: semua property terkunci otomatis, property dinamis ditolak, dan pewarisan hanya boleh dilanjutkan sesama class readonly. Pola method with melengkapi kunci tersebut dengan cara mengubah data secara aman lewat objek baru. Kombinasi readonly class dengan constructor property promotion pada contoh-contoh di atas memang terlihat sangat ringkas; chapter berikutnya membedah cara kerja promotion itu sendiri — fitur yang memangkas tiga tempat penulisan data menjadi satu.