Property public memang mudah dipakai: siapa pun boleh membacanya. Namun kemudahan tersebut berlaku dua arah — kode luar juga bisa mengubah nilainya kapan saja, dan satu penugasan yang lalai cukup untuk merusak state sebuah object. Selama ini pilihan kita hanya dua: public yang terbuka penuh, atau private yang memaksa kita menulis getter agar nilai masih terbaca. PHP 8.4 memperkenalkan asymmetric visibility: visibility untuk operasi baca dan tulis dipisahkan, sehingga sebuah property bisa dibaca dari mana saja tetapi hanya boleh ditulis dari scope class. Pada chapter ini kita pelajari sintaksnya, kombinasi dengan constructor promotion dan property hooks, hingga pola imutabilitas yang lebih ringkas daripada readonly.
Masalah Baca Bebas dan Tulis Terkendali #
Kebutuhan “boleh dibaca siapa saja, tapi hanya boleh diubah pemiliknya” muncul hampir di semua domain: saldo rekening, status pesanan, atau harga produk. PHP 8.4 memenuhi kebutuhan tersebut dengan memperluas sintaks deklarasi property. Mari kita bandingkan dulu pendekatan yang tersedia sebelumnya:
| Pendekatan | Baca dari luar | Tulis dari luar | Catatan |
|---|---|---|---|
public | boleh | boleh | tidak ada kontrol penulisan sama sekali |
private + getter method | lewat method | tidak bisa | menambah boilerplate getter |
readonly | boleh | tidak bisa | hanya boleh diinisialisasi satu kali |
public private(set) | boleh | tidak bisa | tulis hanya dari scope class, boleh berulang |
Asymmetric visibility mengisi celah di baris terakhir: properti tetap terbuka untuk dibaca, sementara penulisan dikunci di dalam class. Berbeda dengan readonly yang hanya mengizinkan inisialisasi satu kali (lihat chapter readonly properties
), property dengan asymmetric visibility boleh ditulis ulang berkali-kali — asalkan dari scope yang berizin.
Sintaks public private(set) #
Bentuk paling umum adalah public private(set). Kata visibility pertama mengatur operasi baca, sedangkan kata di dalam tanda kurung beserta akhiran (set) mengatur operasi tulis:
<?php
class Rekening
{
public private(set) int $saldo;
public function __construct(int $saldoAwal)
{
$this->saldo = $saldoAwal;
}
public function tambahSaldo(int $jumlah): void
{
$this->saldo += $jumlah;
}
}
$rekening = new Rekening(500000);
echo "Saldo awal: {$rekening->saldo}\n";
$rekening->tambahSaldo(250000);
echo "Saldo akhir: {$rekening->saldo}\n";Output yang Dihasilkan:
Saldo awal: 500000
Saldo akhir: 750000Mari kita telaah baris-baris pentingnya:
- Baris 4 (
public private(set) int $saldo;): property$saldodeklarasikan dengan visibility bacapublicdan visibility tulisprivate(set). Bagian(set)inilah penanda asymmetric visibility — tanpa suffix tersebut,privatebiasa akan mengunci baca dan tulis sekaligus. - Baris 7 (
$this->saldo = $saldoAwal;): constructor berada di dalam scope classRekening, sehingga penulisan diperbolehkan. - Baris 12 (
$this->saldo += $jumlah;): methodtambahSaldo()juga berada dalam scope class, jadi compound assignment baca-tulis di sini sah. - Baris 17 dan 20 (
$rekening->saldo): kode luar class membaca$saldosebagaimana propertypublicbiasa — tanpa getter, tanpa method tambahan.
Penulisan dari Luar Class Berakhir Error #
Sekarang kita uji sisi penguncianya. Program berikut sengaja menulis $rekening->saldo dari scope global:
<?php
class Rekening
{
public private(set) int $saldo = 100000;
}
$rekening = new Rekening();
echo "Baca saldo: {$rekening->saldo}\n";
$rekening->saldo = 500000;Output yang Dihasilkan:
Baca saldo: 100000
Fatal error: Uncaught Error: Cannot modify private(set) property Rekening::$saldo from global scope in /home/z/my-project/php-course/content/php/advanced-object-model/asymmetric-visibility/.verify_tmp.php:10
Stack trace:
#0 {main}
thrown in /home/z/my-project/php-course/content/php/advanced-object-model/asymmetric-visibility/.verify_tmp.php on line 10Baris echo berhasil mencetak saldo karena operasi baca memang public. Baris terakhir kemudian melempar Error: Cannot modify private(set) property Rekening::$saldo from global scope — PHP menolak penulisan dari scope yang tidak berizin, dan eksekusi berhenti dengan exit code 255. Kesalahan ini terdeteksi saat runtime, bukan saat parsing, jadi lindungi invarian class Anda dengan disiplin scope, bukan dengan berharap kesalahan tertangkap lebih awal.
Dengan Constructor Promotion #
Asymmetric visibility bisa langsung dipakai pada constructor property promotion . Kita cukup menuliskan kedua visibility pada parameter yang dipromosikan:
<?php
class Produk
{
public function __construct(
public private(set) string $nama,
public private(set) int $harga,
) {}
}
$produk = new Produk("Kopi Gayo", 85000);
echo "{$produk->nama} seharga Rp{$produk->harga}\n";Output yang Dihasilkan:
Kopi Gayo seharga Rp85000Parameter public private(set) string $nama otomatis menjadi property $nama dengan aturan baca-tulis terpisah. Constructor sendiri berjalan di dalam scope class, sehingga inisialisasi awal oleh promotion selalu diperbolehkan tanpa konflik. Hasilnya adalah value object ringkas: tiga baris untuk mendapatkan property yang terbaca publik namun terkunci penulisannya.
Imutabilitas Tanpa readonly #
Pola wither — membuat salinan object dengan satu nilai berubah — menjadi jauh lebih ringan dengan asymmetric visibility. Karena clone dijalankan dari dalam method class, kita boleh menulis property hasil salinan tersebut:
<?php
class Uang
{
public function __construct(public private(set) int $jumlah) {}
public function tambah(int $tambahan): static
{
$baru = clone $this;
$baru->jumlah = $this->jumlah + $tambahan;
return $baru;
}
}
$a = new Uang(1000);
$b = $a->tambah(500);
echo "Objek lama: Rp{$a->jumlah}\n";
echo "Objek baru: Rp{$b->jumlah}\n";Output yang Dihasilkan:
Objek lama: Rp1000
Objek baru: Rp1500Method tambah() membuat salinan lewat clone $this, mengubah $jumlah milik salinan (sah karena masih dalam scope Uang), lalu mengembalikan object baru. Object asli $a tidak pernah tersentuh — perhatikan nilainya tetap 1000 setelah pemanggilan. Dengan readonly pola serupa harus menunggu kelonggaran aturan di PHP 8.3 (reinisialisasi di dalam __clone) atau trik tambahan; dengan private(set), cukup clone, tulis, dan kembalikan.
Varian protected(set) dan Warisan #
Varian protected(set) membuka penulisan untuk scope class beserta seluruh subclass-nya:
<?php
class Entitas
{
public function __construct(public protected(set) string $slug) {}
protected function ubahSlug(string $slugBaru): void
{
$this->slug = $slugBaru;
}
}
class Artikel extends Entitas
{
public function publikasiUlang(): string
{
$this->ubahSlug("belajar-php-84-v2");
return $this->slug;
}
}
$artikel = new Artikel("belajar-php-84");
echo "Sebelum: {$artikel->slug}\n";
echo "Sesudah: " . $artikel->publikasiUlang() . "\n";Output yang Dihasilkan:
Sebelum: belajar-php-84
Sesudah: belajar-php-84-v2Method publikasiUlang() di class Artikel berhasil memodifikasi $slug karena visibility tulis protected(set) mengikuti aturan protected pada umumnya. Perlu dicatat, ubahSlug() sengaja diletakkan di parent dengan visibility protected — subclass memanggilnya alih-alih menulis $this->slug langsung, yang menunjukkan desain di mana parent mengendalikan bagaimana perubahan terjadi.
Sebaliknya, private(set) lebih ketat daripada yang diperkirakan sebagian pembaca: hanya scope class pendeklarasi yang boleh menulis. Subclass tetap terkunci:
<?php
class Entitas
{
public private(set) string $slug = "artikel-baru";
}
class Artikel extends Entitas
{
public function gantiSlug(): void
{
$this->slug = "artikel-lama";
}
}
(new Artikel())->gantiSlug();Output yang Dihasilkan:
Fatal error: Uncaught Error: Cannot modify private(set) property Entitas::$slug from scope Artikel in /home/z/my-project/php-course/content/php/advanced-object-model/asymmetric-visibility/.verify_tmp.php:12
Stack trace:
#0 /home/z/my-project/php-course/content/php/advanced-object-model/asymmetric-visibility/.verify_tmp.php(16): Artikel->gantiSlug()
#1 {main}
thrown in /home/z/my-project/php-course/content/php/advanced-object-model/asymmetric-visibility/.verify_tmp.php on line 12Meskipun property $slug terbaca publik dan class Artikel mewarisinya, penulisan dari method subclass tetap ditolak dengan Error ... from scope Artikel. Jika subclass memang perlu mengubah nilainya, gunakan protected(set) sejak awal atau sediakan method pengubah di class pendeklarasi.
Kombinasi dengan Property Hooks #
Asymmetric visibility dirancang saling melengkapi dengan property hooks . Kedua fitur bisa dipasang pada satu property sekaligus — visibility mengatur siapa yang boleh menulis, hook mengatur apa yang terjadi ketika penulisan itu sah:
<?php
class Harga
{
// get publik, set hanya dari scope class, dan melewati hook set
public private(set) int $nilai {
set (int $baru) {
if ($baru < 0) {
throw new InvalidArgumentException("Harga tidak boleh negatif");
}
$this->nilai = $baru;
}
}
}Hook set pada contoh di atas memvalidasi bahwa nilai baru tidak negatif; penulisan dari luar class tetap ditolak oleh private(set) sebelum hook sempat berjalan. Aturan mainnya satu arah: visibility tulis tidak boleh lebih longgar daripada visibility baca — penulisan private public(set) ... langsung ditolak PHP dengan pesan visibility of property must not be weaker than set visibility.
Ringkasan #
Asymmetric visibility memberi PHP kontrol granular yang selama ini mengharuskan kita memilih antara keterbukaan dan keamanan: public private(set) untuk nilai yang bebas dibaca namun dikunci penulisannya, protected(set) bila hierarki class perlu ikut menulis, dan kombinasi dengan hooks untuk validasi setiap penulisan yang sah. Fitur ini melengkapi — bukan menggantikan — readonly: gunakan readonly untuk nilai yang benar-benar tidak berubah, dan asymmetric visibility untuk nilai yang berubah hanya lewat kontrol class. Berikutnya kita berpindah dari pengelolaan property ke pengelolaan sekumpulan nilai tetap: enumeration.