Kodeloka
  1. Kodeloka
  2. Kelas PHP
  3. Materi

2 · OOP & Struktur Kode · Modul 6 dari 25

Pemrograman Berorientasi Objek (OOP) Modern: Readonly, Asymmetric Visibility, dan Property Hooks

Merancang class yang ringkas dan tangguh di PHP 8 menggunakan Constructor Property Promotion, Readonly Classes & clone($this, [...]) (PHP 8.5), Asymmetric Visibility public private(set) (PHP 8.4), Property Hooks (PHP 8.4), serta interface dan trait.

1. Class Modern dan Constructor Property Promotion (Sejak PHP 8.0)

Di PHP versi lama, membuat sebuah class sederhana dengan 4 properti mengharuskan kita menulis nama properti yang sama sebanyak empat kali (deklarasi properti, parameter konstruktor, penugasan $this->a = $a, dan getter).

Sejak PHP 8.0, fitur Constructor Property Promotion memungkinkan Anda mendeklarasikan visibilitas (public, protected, private) langsung di dalam parameter __construct(), dan PHP otomatis membuatkan properti sekaligus mengisi nilainya!

<?php

declare(strict_types=1);

class KursusKodeloka
{
    public function __construct(
        public string $slug,
        public string $judul,
        public int $jumlahModul,
        private string $kodeInternal = 'KDLK-00',
    ) {}

    public function infoRingkas(): string
    {
        return sprintf('%s (%d modul)', $this->judul, $this->jumlahModul);
    }
}

$kursus = new KursusKodeloka('php', 'Pemrograman PHP Modern', 15);
echo $kursus->infoRingkas(); // "Pemrograman PHP Modern (15 modul)"
💡 Fitur Ergonomis Baru (Sejak PHP 8.4): new MyClass()->method() Tanpa Kurung Tambahan!

Sebelum PHP 8.4, jika Anda ingin langsung memanggil metode saat membuat objek baru, Anda wajib membungkus new dengan tanda kurung ekstra: (new KursusKodeloka(...))->infoRingkas(). Sejak PHP 8.4, Anda dapat langsung menulisnya dengan bersih tanpa kurung pembungkus: echo new KursusKodeloka('php', 'PHP Modern', 15)->infoRingkas();

2. Immutability dengan readonly Class dan clone() Baru (PHP 8.2 & 8.5)

Objek yang nilainya tidak dapat diubah lagi setelah dibuat (Immutable Value Object / Data Transfer Object) sangat aman karena Anda dapat meneruskannya ke fungsi mana pun tanpa khawatir datanya diubah secara diam-diam.

  • Sejak PHP 8.1, Anda dapat menandai properti individual dengan kata kunci readonly.
  • Sejak PHP 8.2, menandai seluruh class sebagai readonly class otomatis menjadikan seluruh properti di dalamnya bersifat readonly!
  • Dan sejak PHP 8.5, ketika Anda ingin membuat salinan dari objek readonly sambil memperbarui satu propertinya saja (pola with-er), fungsi clone($objek, ['properti' => $nilaiBaru]) kini mendukung pembaruan properti secara langsung saat penyalinan!
<?php

declare(strict_types=1);

readonly class KonfigurasiKoneksi
{
    public function __construct(
        public string $host,
        public int $port = 5432,
        public int $timeoutDetik = 5,
    ) {}

    // Pola "with-er" untuk objek immutable:
    // Sejak PHP 8.5, clone() menerima argumen ke-2 berupa array properti yang ingin diperbarui!
    public function denganTimeout(int $detikBaru): self
    {
        return clone($this, [
            'timeoutDetik' => $detikBaru,
        ]);
    }
}

$koneksiUtama = new KonfigurasiKoneksi('db.kodeloka.internal');
// $koneksiUtama->timeoutDetik = 10; // Error! Properti readonly tidak boleh diubah di tempat.

$koneksiLaporan = $koneksiUtama->denganTimeout(30);
echo $koneksiUtama->timeoutDetik;   // Tetap 5
echo $koneksiLaporan->timeoutDetik; // 30

3. Visibilitas Asimetris (public private(set)) dan Property Hooks (Sejak PHP 8.4)

Bagaimana jika sebuah objek bukan objek immutable (nilai di dalamnya masih boleh berubah melalui metode bisnis internal class), tetapi Anda ingin pihak luar bisa membaca nilainya secara langsung tanpa boleh mengubahnya dari luar?

Dulu kita harus membuat properti private lalu menulis metode getSaldo() yang bertele-tele. Sejak PHP 8.4, hadir dua fitur revolusioner:

A. Asymmetric Visibility (public private(set) — Sejak PHP 8.4)

Anda dapat mengatur hak baca (read) dan hak tulis (write/set) sebuah properti secara terpisah:

<?php

declare(strict_types=1);

class DompetBelajar
{
    // Boleh DIBACA oleh siapa saja (public),
    // tetapi HANYA boleh DIUBAH dari dalam class ini sendiri (private(set))!
    public private(set) int $poin = 0;

    public function tambahPoinSelesaiModul(int $tambahan = 10): void
    {
        if ($tambahan <= 0) {
            throw new InvalidArgumentException('Poin harus positif.');
        }
        $this->poin += $tambahan;
    }
}

$dompet = new DompetBelajar();
$dompet->tambahPoinSelesaiModul(25);
echo $dompet->poin; // 25 (Bisa dibaca langsung tanpa getPoin()!)
// $dompet->poin = 9999; // Error! Tidak boleh diubah dari luar class DompetBelajar.

B. Property Hooks (get dan set — Sejak PHP 8.4)

Property Hooks memungkinkan Anda menempelkan logika validasi/normalisasi saat properti diisi (set), atau membuat properti terhitung (virtual property dengan get), langsung pada deklarasi properti tanpa mengubah cara pemanggil mengakses $objek->properti:

<?php

declare(strict_types=1);

class Pengguna
{
    // 1. Backed property dengan hook 'set' untuk menormalisasi dan memvalidasi surel
    public string $surel {
        set (string $nilai) {
            $bersih = strtolower(trim($nilai));
            if (!filter_var($bersih, FILTER_VALIDATE_EMAIL)) {
                throw new InvalidArgumentException("Format surel tidak sah: {$nilai}");
            }
            $this->surel = $bersih;
        }
    }

    // 2. Virtual property (hanya punya hook 'get', tidak memakan memori penyimpanan tersendiri)
    public string $namaLengkap {
        get => trim("{$this->namaDepan} {$this->namaBelakang}");
    }

    public function __construct(
        public string $namaDepan,
        public string $namaBelakang,
        string $surel,
    ) {
        $this->surel = $surel; // Otomatis melewati hook 'set' di atas!
    }
}

$user = new Pengguna('Nadia', 'Pratama', '  [email protected] ');
echo $user->surel;       // "[email protected]"
echo $user->namaLengkap; // "Nadia Pratama"

4. Kontrak dengan interface, Pewarisan abstract class (#[\Override]), dan trait

  • interface (Kontrak Kemampuan): Mendefinisikan tanda tangan metode publik yang wajib diimplementasikan oleh sebuah class (class PengirimSmtp implements PengirimNotifikasi). Satu class dapat mengimplementasikan banyak interface.
  • abstract class & #[\Override] (Sejak PHP 8.3): Ketika subkelas menimpa metode milik kelas induk atau mengimplementasikan metode interface, tambahkan atribut #[\Override] di atas metode tersebut! Dengan #[\Override], PHP akan segera memberi tahu Anda jika nama metode salah ketik atau jika kelas induk mengubah nama metodenya di masa depan.
  • trait: Mekanisme penggunaan ulang kode secara horizontal (use MencatatWaktu;) ketika beberapa class yang tidak memiliki hubungan induk-anak membutuhkan metode pembantu yang sama.

Latihan Praktik: Merancang Entitas Produk dengan Asymmetric Visibility dan Property Hooks

Buatlah berkas entitas-produk.php:

  1. Buat class Produk yang menggunakan constructor property promotion untuk public readonly string $kodeSku.
  2. Tambahkan properti public private(set) int $stok = 0 dan metode tambahStok(int $jumlah) serta kurangiStok(int $jumlah).
  3. Tambahkan properti $hargaRupiah dengan property hook set yang menolak nilai negatif (InvalidArgumentException), serta virtual property $hargaTerformat dengan hook get yang mengembalikan 'Rp' . number_format($this->hargaRupiah, 0, ',', '.').