Visibilitas

public, private, protected, dan prinsip enkapsulasi.

Daftar isi

Setiap properti dan metode kelas memiliki tingkat visibilitas yang menentukan dari mana anggota tersebut boleh diakses. Topik ini membahas tiga kata kunci visibilitas pada PHP, yaitu public, protected, dan private, beserta prinsip enkapsulasi (encapsulation) yang mendasari penggunaannya. Setelah topik ini, batas antara keadaan internal objek dan antarmuka yang boleh dipakai pihak luar dapat ditetapkan secara terencana, bukan sekadar terbentuk mengikuti kebiasaan menulis kode.

Tiga tingkat visibilitas#

Visibilitas ditulis pada deklarasi anggota, sebelum nama properti atau sebelum nama metode. Ketiga tingkatnya dirangkum pada tabel berikut.

TingkatKelas sendiriKelas turunanKode di luar kelas
publicdapat diaksesdapat diaksesdapat diakses
protecteddapat diaksesdapat diaksestidak dapat diakses
privatedapat diaksestidak dapat diaksestidak dapat diakses

Anggota public membentuk antarmuka kelas, yaitu bagian yang boleh dipakai kode lain tanpa syarat. Anggota private hanya terlihat di dalam kelas yang mendeklarasikannya, sehingga cocok untuk keadaan internal yang nilainya harus dijaga. Anggota protected berada di antara keduanya, yaitu tetap tersembunyi dari luar tetapi dapat dipakai oleh kelas yang mewarisinya. Sejak PHP 7.1, ketiga kata kunci ini juga berlaku untuk konstanta kelas. Properti yang dipromosikan pada constructor wajib membawa salah satu kata kunci ini secara eksplisit. Kata kunci itulah yang membedakan properti hasil promosi dari parameter biasa.

Pembatasan ini ditegakkan oleh PHP pada saat akses dilakukan, bukan sekadar kesepakatan penulisan. Skrip berikut mencoba membaca properti private dan memanggil metode private dari kode di luar kelas, lalu menangkap errornya agar program tetap berjalan dan pesannya dapat diperiksa.

php akses-di-luar.php
<?php
declare(strict_types=1);

class Buku
{
    private string $kode = 'BK-001';
    private int $stok = 2;

    public function stokTersedia(): int
    {
        return $this->stok;
    }

    private function perbaruiKode(string $kode): void
    {
        $this->kode = $kode;
    }
}

$buku = new Buku();

try {
    echo $buku->stok;
} catch (Throwable $e) {
    echo get_class($e), ': ', $e->getMessage(), "\n";
}

try {
    $buku->perbaruiKode('BK-999');
} catch (Throwable $e) {
    echo get_class($e), ': ', $e->getMessage(), "\n";
}

echo 'Stok tersedia: ', $buku->stokTersedia(), "\n";
text Keluaran
Error: Cannot access private property Buku::$stok
Error: Call to private method Buku::perbaruiKode() from global scope
Stok tersedia: 2

Kedua baris error pada keluaran menunjukkan bahwa akses dari luar kelas dihentikan sebelum properti atau metode dijalankan. Baris terakhir membuktikan bahwa anggota private tetap dapat digunakan dari dalam kelasnya, yaitu metode stokTersedia() membaca $this->stok tanpa hambatan.

Menyembunyikan keadaan internal#

Visibilitas berguna ketika digunakan untuk enkapsulasi, yaitu menyembunyikan keadaan internal objek dan hanya membuka operasi yang bermakna. Anggota public pada contoh sebelumnya dibiarkan tanpa penjagaan, sehingga kode luar dapat menuliskan $buku->stok = -5; dan membuat keadaan yang tidak masuk akal. Dengan membuat $stok bersifat private, satu-satunya cara mengubah stok adalah melalui metode yang menetapkan aturannya, misalnya peminjaman hanya boleh terjadi bila stok masih ada.

Skrip berikut menerapkan pola tersebut. Constructor menggunakan constructor property promotion dengan visibilitas private, sehingga properti $stok langsung tersembunyi sejak objek dibuat. Metode pinjamkan() menetapkan bahwa peminjaman gagal bila stok kosong, sedangkan penurunan stok hanya terjadi setelah aturan tersebut terpenuhi.

php stok-buku.php
<?php
declare(strict_types=1);

class Buku
{
    public function __construct(
        private string $kode,
        private int $stok = 2,
    ) {
    }

    public function pinjamkan(): bool
    {
        if ($this->stok < 1) {
            return false;
        }
        $this->stok--;
        return true;
    }

    public function kembalikan(): void
    {
        $this->stok++;
    }

    public function stok(): int
    {
        return $this->stok;
    }
}

$buku = new Buku('BK-001');

if ($buku->pinjamkan()) {
    echo 'pinjam pertama berhasil', "\n";
}
if ($buku->pinjamkan()) {
    echo 'pinjam kedua berhasil', "\n";
}
if ($buku->pinjamkan()) {
    echo 'pinjam ketiga berhasil', "\n";
} else {
    echo 'pinjam ketiga gagal karena stok kosong', "\n";
}

$buku->kembalikan();
echo 'Stok sekarang: ', $buku->stok(), "\n";
text Keluaran
pinjam pertama berhasil
pinjam kedua berhasil
pinjam ketiga gagal karena stok kosong
Stok sekarang: 1

Keluaran memperlihatkan aturan itu bekerja. Dua salinan yang tersedia cukup untuk dua peminjaman, peminjaman ketiga ditolak oleh metode itu sendiri, dan satu pengembalian menaikkan stok menjadi satu. Kode pemanggil tidak perlu tahu cara stok disimpan karena yang diperlukan hanyalah pertanyaan yang bermakna, yaitu apakah peminjaman berhasil.

protected untuk kelas turunan#

Anggota protected menyatukan dua kebutuhan yang sering muncul bersamaan, yaitu menyembunyikan keadaan dari kode luar sekaligus memperbolehkan kelas turunannya memakainya. Kelas turunan dibuat dengan kata kunci extends, dan penjelasan lengkap mekanisme pewarisannya dibahas pada topik pewarisan. Untuk visibilitas, hal yang penting di sini adalah kelas turunan diperlakukan sebagai bagian dari kelas induk, sehingga $this di dalam kelas turunan masih dapat mengakses anggota protected milik induknya.

Skrip berikut mendeklarasikan kelas Pengguna dengan properti protected bernama $nama, lalu kelas Anggota yang mewarisinya dan membaca $this->nama dari metodenya sendiri. Akses dari luar tetap ditolak, dan errornya ditangkap untuk memperlihatkan pesannya.

php pengguna-dan-anggota.php
<?php
declare(strict_types=1);

class Pengguna
{
    public function __construct(protected string $nama)
    {
    }

    public function sapa(): string
    {
        return "Halo, {$this->nama}.";
    }
}

class Anggota extends Pengguna
{
    public function kartuPerpustakaan(): string
    {
        return "Kartu atas nama {$this->nama}, berlaku satu tahun.";
    }
}

$anggota = new Anggota('Dewi');
echo $anggota->sapa(), "\n";
echo $anggota->kartuPerpustakaan(), "\n";

try {
    echo $anggota->nama;
} catch (Throwable $e) {
    echo get_class($e), ': ', $e->getMessage(), "\n";
}
text Keluaran
Halo, Dewi.
Kartu atas nama Dewi, berlaku satu tahun.
Error: Cannot access protected property Anggota::$nama

Dua metode dari dua kelas yang berbeda berhasil membaca $this->nama, sedangkan pembacaan dari luar menghasilkan error. Pesan errornya menyebut Anggota::$nama karena PHP menilai akses dari sudut pandang objek yang sedang dipakai. Aksesnya tetap ditolak karena kode yang melakukan akses berada di luar kedua kelas tersebut.

Kelas sendiriKelas turunanKode di luarpublicyayayaprotectedyayatidakprivateyatidaktidak
Cakupan akses tiga tingkat visibilitas dari kelas sendiri, kelas turunan, dan kode di luar kelas

Kesalahan umum#

  • Membuat semua properti public demi kepraktisan. Keadaan internal yang terbuka penuh membuat aturan seperti batas stok tidak dapat ditegakkan, karena penulisan langsung dari luar melewati metode penjaganya. Jadikan properti private dan sediakan metode yang menerapkan aturannya.
  • Menyediakan getter dan setter untuk seluruh properti secara otomatis. Visibilitas private yang segera diikuti sepasang metode pembaca dan penulis tanpa validasi hanya memindahkan masalah ke tempat lain tanpa penjagaan yang nyata. Sediakan metode akses hanya untuk data yang memang perlu dibaca atau diubah, dan berikan pemeriksaan pada jalur yang mengubah keadaan.
  • Mengakses properti private induk dari kelas turunan. Pesan errornya menyebut Cannot access private property, padahal penulisnya yakin berada di dalam kelas yang berhak. Hal ini terjadi karena private menutup akses termasuk dari turunannya. Pilih protected untuk anggota yang memang disiapkan untuk kelas turunan.
  • Memperlakukan protected sebagai public kedua. Anggota protected tetap tersembunyi dari kode luar, sehingga keputusan menjadikannya protected berarti komitmen bahwa bentuk keadaan tersebut akan dipertahankan bagi kelas turunannya. Ubah anggota protected dengan kehati-hatian yang sama seperti anggota public, karena kelas turunannya bergantung padanya.

Ringkasan#

  • public, protected, dan private menetapkan siapa yang boleh mengakses sebuah anggota, yaitu semuanya, kelas beserta turunannya, atau kelas yang mendeklarasikannya saja.
  • PHP menegakkan visibilitas pada saat akses, dan akses yang melanggar memicu Error yang pesannya menyebut properti atau metode yang bersangkutan.
  • Enkapsulasi menyembunyikan keadaan internal sehingga perubahan hanya dapat terjadi melalui metode yang menerapkan aturan, seperti penjagaan stok pada pinjamkan().
  • protected menyediakan jalan tengah untuk kelas turunan, sedangkan anggota private sepenuhnya milik kelas yang mendeklarasikannya.

Referensi#