Kelas #
Kelas adalah pondasi dari pemrograman berorientasi objek di PHP — cetak biru yang mendefinisikan data (property) dan perilaku (method) yang dimiliki setiap objek yang dibuat darinya. Hampir semua framework PHP modern — Laravel, Symfony, CodeIgniter — dibangun sepenuhnya di atas kelas. Memahami kelas bukan sekadar tahu sintaksnya, tapi juga paham prinsip yang membuatnya berguna: enkapsulasi yang menjaga data konsisten, pewarisan yang menghindari duplikasi, polimorfisme yang membuat kode fleksibel, dan kapan sebuah konsep memang layak dijadikan kelas, bukan sekadar fungsi. Artikel ini membahas semua aspek kelas PHP secara mendalam, termasuk fitur-fitur modern seperti constructor promotion, readonly property, dan anonymous class.
Anatomi Kelas PHP #
Sebuah kelas PHP terdiri dari beberapa bagian yang memiliki aturan dan urutan konvensionalnya sendiri:
flowchart TD
A[class NamaKelas] --> B[extends ParentClass]
A --> C[implements Interface]
A --> D[Isi Kelas]
D --> D1["Konstanta\nconst NAMA = nilai"]
D --> D2["Property\npublic/protected/private"]
D --> D3["Constructor\n__construct()"]
D --> D4["Method publik\nAPI eksternal kelas"]
D --> D5["Method protected\nuntuk turunan"]
D --> D6["Method private\nimplementasi internal"]
style D1 fill:#dbeafe
style D2 fill:#dcfce7
style D3 fill:#fef9c3
style D4 fill:#f3e8ff
style D5 fill:#ffedd5
style D6 fill:#fee2e2Mendefinisikan Kelas #
Kelas didefinisikan dengan keyword class, diikuti nama kelas dalam format PascalCase. Setiap kelas sebaiknya berada di file tersendiri dengan nama file yang sama dengan nama kelas.
<?php
class Produk
{
// Property — data yang dimiliki setiap instance
public string $nama;
public float $harga;
private int $stok;
protected string $kategori;
// Constructor — dijalankan saat new Produk(...)
public function __construct(
string $nama,
float $harga,
int $stok = 0,
string $kategori = 'umum',
) {
$this->nama = $nama;
$this->harga = $harga;
$this->stok = $stok;
$this->kategori = $kategori;
}
// Method publik — antarmuka ke luar
public function tersedia(): bool
{
return $this->stok > 0;
}
public function hargaFormat(): string
{
return 'Rp ' . number_format($this->harga, 0, ',', '.');
}
public function getStok(): int
{
return $this->stok;
}
// Method privat — implementasi internal
private function validasiStok(int $jumlah): void
{
if ($jumlah < 0) {
throw new \InvalidArgumentException("Stok tidak boleh negatif");
}
}
public function tambahStok(int $jumlah): void
{
$this->validasiStok($jumlah);
$this->stok += $jumlah;
}
public function kurangiStok(int $jumlah): void
{
$this->validasiStok($jumlah);
if ($jumlah > $this->stok) {
throw new \RuntimeException("Stok tidak mencukupi");
}
$this->stok -= $jumlah;
}
}
// Membuat instance (objek)
$laptop = new Produk('Laptop ProBook', 15_000_000, 5, 'elektronik');
echo $laptop->nama; // Laptop ProBook
echo $laptop->hargaFormat(); // Rp 15.000.000
var_dump($laptop->tersedia()); // bool(true)
$laptop->tambahStok(3);
echo $laptop->getStok(); // 8
Constructor Promotion (PHP 8.0+) #
Constructor promotion mempersingkat deklarasi property yang nilainya langsung diambil dari parameter constructor. Daripada mendeklarasikan property di atas lalu mengassignnya satu per satu di constructor, cukup tambahkan visibility modifier pada parameter constructor:
<?php
// ANTI-PATTERN: cara lama — property dideklarasikan dua kali
class ProdukLama
{
public string $nama;
public float $harga;
private int $stok;
public function __construct(string $nama, float $harga, int $stok = 0)
{
$this->nama = $nama;
$this->harga = $harga;
$this->stok = $stok;
}
}
// BENAR: constructor promotion — lebih ringkas, hasil identik
class Produk
{
public function __construct(
public string $nama,
public float $harga,
private int $stok = 0,
protected string $kategori = 'umum',
) {
// body constructor masih bisa ada untuk validasi awal
if ($harga < 0) {
throw new \InvalidArgumentException("Harga tidak boleh negatif");
}
}
}
// Penggunaan identik
$p = new Produk('Mouse', 250_000, 10);
echo $p->nama; // Mouse
echo $p->harga; // 250000
Constructor promotion juga bisa dikombinasikan dengan parameter non-promoted dalam satu constructor — parameter promoted dibedakan oleh kehadiran visibility modifier.
Visibility (Akses Modifier) #
PHP memiliki tiga level visibility yang mengontrol dari mana property dan method bisa diakses:
| Modifier | Dari kelas sendiri | Dari kelas turunan | Dari luar kelas |
|---|---|---|---|
public | ✓ | ✓ | ✓ |
protected | ✓ | ✓ | ✗ |
private | ✓ | ✗ | ✗ |
<?php
class AkunBank
{
private float $saldo = 0;
private array $riwayat = [];
protected string $nomorRekening;
public function __construct(string $nomorRekening, float $saldoAwal = 0)
{
$this->nomorRekening = $nomorRekening;
$this->saldo = $saldoAwal;
}
// Publik — API yang boleh dipakai siapapun
public function getSaldo(): float
{
return $this->saldo;
}
public function setor(float $jumlah): void
{
$this->validasiJumlah($jumlah);
$this->saldo += $jumlah;
$this->catatRiwayat('setor', $jumlah);
}
public function tarik(float $jumlah): void
{
$this->validasiJumlah($jumlah);
if ($jumlah > $this->saldo) {
throw new \RuntimeException("Saldo tidak mencukupi");
}
$this->saldo -= $jumlah;
$this->catatRiwayat('tarik', $jumlah);
}
// Private — hanya untuk implementasi internal kelas ini
private function validasiJumlah(float $jumlah): void
{
if ($jumlah <= 0) {
throw new \InvalidArgumentException("Jumlah harus positif");
}
}
private function catatRiwayat(string $tipe, float $jumlah): void
{
$this->riwayat[] = [
'tipe' => $tipe,
'jumlah' => $jumlah,
'waktu' => time(),
'saldo' => $this->saldo,
];
}
public function getRiwayat(): array
{
return $this->riwayat; // kembalikan salinan, bukan referensi
}
}
$akun = new AkunBank('BCA-001', 1_000_000);
$akun->setor(500_000);
$akun->tarik(200_000);
echo $akun->getSaldo(); // 1300000
// $akun->saldo = 999999999; // Fatal Error — private property
// $akun->validasiJumlah(100); // Fatal Error — private method
Prinsip Enkapsulasi #
Enkapsulasi bukan sekadar menyembunyikan property — ini tentang menjaga invariant objek: kondisi yang harus selalu benar selama objek hidup.
<?php
// ANTI-PATTERN: semua public — tidak ada proteksi invariant
class SuhukuLama
{
public float $celsius;
public function __construct(float $celsius)
{
$this->celsius = $celsius; // bisa diset ke -999 tanpa validasi
}
}
$suhu = new SuhukuLama(25);
$suhu->celsius = -999; // valid secara PHP, tapi tidak masuk akal fisika
// BENAR: enkapsulasi menjaga invariant
class Suhu
{
private float $celsius;
// Suhu absolut minimum adalah -273.15°C (0 Kelvin)
private const ABSOLUT_MINIMUM = -273.15;
public function __construct(float $celsius)
{
$this->setCelsius($celsius);
}
public function getCelsius(): float { return $this->celsius; }
public function getFahrenheit(): float
{
return ($this->celsius * 9 / 5) + 32;
}
public function getKelvin(): float
{
return $this->celsius - self::ABSOLUT_MINIMUM;
}
public function setCelsius(float $celsius): void
{
if ($celsius < self::ABSOLUT_MINIMUM) {
throw new \InvalidArgumentException(
"Suhu tidak boleh di bawah " . self::ABSOLUT_MINIMUM . "°C"
);
}
$this->celsius = $celsius;
}
}
$suhu = new Suhu(25);
echo $suhu->getFahrenheit(); // 77
echo $suhu->getKelvin(); // 298.15
// $suhu->setCelsius(-300); // InvalidArgumentException
Pewarisan (Inheritance) #
Pewarisan memungkinkan kelas anak untuk mewarisi property dan method dari kelas induk, lalu memperluas atau mengubah perilakunya. PHP hanya mendukung pewarisan tunggal — satu kelas hanya bisa extend satu kelas induk.
<?php
class Kendaraan
{
public function __construct(
protected string $merk,
protected int $tahun,
protected float $harga,
) {}
public function getInfo(): string
{
return "{$this->merk} ({$this->tahun})";
}
public function getHargaFormat(): string
{
return 'Rp ' . number_format($this->harga, 0, ',', '.');
}
// Method yang bisa di-override oleh kelas anak
public function deskripsi(): string
{
return $this->getInfo() . ' — ' . $this->getHargaFormat();
}
}
class Mobil extends Kendaraan
{
public function __construct(
string $merk,
int $tahun,
float $harga,
private int $jumlahPintu = 4,
) {
parent::__construct($merk, $tahun, $harga); // wajib panggil parent
}
// Override method induk
public function deskripsi(): string
{
return parent::deskripsi() . ", {$this->jumlahPintu} pintu";
}
}
class MotorListrik extends Kendaraan
{
public function __construct(
string $merk,
int $tahun,
float $harga,
private int $kapasitasBaterai, // kWh
) {
parent::__construct($merk, $tahun, $harga);
}
public function deskripsi(): string
{
return parent::deskripsi() . ", baterai {$this->kapasitasBaterai} kWh";
}
public function estimasiJarak(): int
{
return $this->kapasitasBaterai * 6; // estimasi kasar: 6 km per kWh
}
}
$avanza = new Mobil('Toyota Avanza', 2023, 250_000_000, 5);
$vespaPrime = new MotorListrik('Vespa Elettrica', 2023, 95_000_000, 4);
echo $avanza->deskripsi();
// Toyota Avanza (2023) — Rp 250.000.000, 5 pintu
echo $vespaPrime->deskripsi();
// Vespa Elettrica (2023) — Rp 95.000.000, baterai 4 kWh
echo $vespaPrime->estimasiJarak(); // 24
// instanceof cek apakah object adalah instance dari kelas atau induknya
var_dump($avanza instanceof Mobil); // true
var_dump($avanza instanceof Kendaraan); // true — karena Mobil extends Kendaraan
var_dump($avanza instanceof MotorListrik); // false
parent:: dan Memanggil Method Induk
#
<?php
class Logger
{
public function log(string $pesan): void
{
echo "[LOG] $pesan\n";
}
}
class DatabaseLogger extends Logger
{
public function log(string $pesan): void
{
parent::log($pesan); // panggil implementasi induk dulu
$this->simpanKeDatabase($pesan); // lalu tambah perilaku sendiri
}
private function simpanKeDatabase(string $pesan): void
{
// simpan ke DB...
}
}
Abstract Class #
Kelas abstract adalah kelas yang tidak bisa di-instantiate langsung — hanya bisa dijadikan induk. Method abstract adalah method tanpa implementasi yang harus di-override oleh kelas anak.
<?php
abstract class Pembayaran
{
protected float $jumlah;
protected string $referensi;
public function __construct(float $jumlah)
{
if ($jumlah <= 0) {
throw new \InvalidArgumentException("Jumlah harus positif");
}
$this->jumlah = $jumlah;
$this->referensi = $this->generateReferensi();
}
// Abstract — setiap kelas anak WAJIB mengimplementasikan ini
abstract public function proses(): bool;
abstract public function namaMetode(): string;
// Method konkret — tersedia untuk semua kelas anak
public function getReferensi(): string
{
return $this->referensi;
}
public function getJumlah(): float
{
return $this->jumlah;
}
private function generateReferensi(): string
{
return strtoupper(uniqid('PAY-'));
}
// Template method — orkestrasi yang ditentukan induk
final public function bayar(): array
{
$berhasil = $this->proses();
return [
'referensi' => $this->referensi,
'metode' => $this->namaMetode(),
'jumlah' => $this->jumlah,
'status' => $berhasil ? 'sukses' : 'gagal',
];
}
}
class PembayaranTransfer extends Pembayaran
{
public function __construct(
float $jumlah,
private string $rekeningTujuan,
) {
parent::__construct($jumlah);
}
public function proses(): bool
{
// Logika transfer bank
return true;
}
public function namaMetode(): string
{
return 'Transfer Bank ke ' . $this->rekeningTujuan;
}
}
class PembayaranGopay extends Pembayaran
{
public function __construct(
float $jumlah,
private string $nomorHp,
) {
parent::__construct($jumlah);
}
public function proses(): bool
{
// Logika Gopay API
return true;
}
public function namaMetode(): string
{
return 'GoPay ' . $this->nomorHp;
}
}
// new Pembayaran(100000); // Fatal Error — kelas abstract tidak bisa di-instantiate
$transfer = new PembayaranTransfer(500_000, 'BCA-1234567890');
$hasil = $transfer->bayar();
print_r($hasil);
// Array ( [referensi] => PAY-xxx [metode] => Transfer Bank... [jumlah] => 500000 [status] => sukses )
final — Mencegah Override dan Pewarisan
#
Keyword final pada kelas mencegah kelas tersebut di-extend. Pada method, mencegah method tersebut di-override oleh kelas anak:
<?php
// Final class — tidak bisa di-extend
final class UUID
{
private string $value;
public function __construct()
{
$this->value = sprintf(
'%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand(0, 0xffff), mt_rand(0, 0xffff),
mt_rand(0, 0xffff),
mt_rand(0, 0x0fff) | 0x4000,
mt_rand(0, 0x3fff) | 0x8000,
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
);
}
public function __toString(): string
{
return $this->value;
}
}
// class UUID4 extends UUID {} // Fatal Error — final class
// Final method — bisa di-extend kelasnya, tapi method ini tidak bisa di-override
class Konfigurasi
{
final public function getVersi(): string
{
return '2.0.0'; // versi tidak boleh diubah oleh kelas anak
}
}
class KonfigurasiLokal extends Konfigurasi
{
// public function getVersi(): string { } // Fatal Error — override final method
}
Property dan Method Statis #
Static property dan method adalah milik kelas, bukan instance. Bisa diakses tanpa membuat objek menggunakan NamaKelas::$property atau NamaKelas::method().
<?php
class Koneksi
{
private static ?PDO $instance = null;
private static int $jumlahQuery = 0;
// Singleton pattern — hanya satu koneksi di seluruh aplikasi
public static function dapatkan(): PDO
{
if (self::$instance === null) {
self::$instance = new PDO(
'mysql:host=localhost;dbname=myapp',
'root',
'',
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
}
return self::$instance;
}
public static function tambahQuery(): void
{
self::$jumlahQuery++;
}
public static function getJumlahQuery(): int
{
return self::$jumlahQuery;
}
}
// Semua bagian aplikasi mendapat PDO yang sama
$db1 = Koneksi::dapatkan();
$db2 = Koneksi::dapatkan();
var_dump($db1 === $db2); // true — instance yang sama
echo Koneksi::getJumlahQuery(); // 0
self:: vs static:: — Late Static Binding
#
<?php
class Induk
{
public static function buat(): static
{
return new static(); // static:: — menggunakan kelas yang dipanggil
}
public static function namaKelas(): string
{
return static::class; // nama kelas aktual saat runtime
}
}
class Anak extends Induk
{
public string $tipe = 'anak';
}
$induk = Induk::buat();
$anak = Anak::buat(); // mengembalikan instance Anak, bukan Induk
var_dump($induk instanceof Induk); // true
var_dump($anak instanceof Anak); // true
echo Anak::namaKelas(); // "Anak" — bukan "Induk"
Magic Method #
Magic method adalah method khusus yang dipanggil secara otomatis oleh PHP dalam situasi tertentu. Namanya selalu diawali dengan dua underscore __.
<?php
class Koleksi
{
private array $items = [];
public function __construct(array $items = [])
{
$this->items = $items;
}
// Dipanggil saat objek digunakan dalam konteks string
public function __toString(): string
{
return implode(', ', $this->items);
}
// Dipanggil saat isset($obj->nama) atau empty($obj->nama)
public function __isset(string $nama): bool
{
return isset($this->items[$nama]);
}
// Dipanggil saat akses $obj->nama untuk property yang tidak ada
public function __get(string $nama): mixed
{
return $this->items[$nama] ?? null;
}
// Dipanggil saat $obj->nama = nilai untuk property yang tidak ada
public function __set(string $nama, mixed $nilai): void
{
$this->items[$nama] = $nilai;
}
// Dipanggil saat unset($obj->nama) untuk property yang tidak ada
public function __unset(string $nama): void
{
unset($this->items[$nama]);
}
// Dipanggil saat objek digunakan sebagai fungsi: $obj()
public function __invoke(mixed $item): static
{
$baru = clone $this;
$baru->items[] = $item;
return $baru;
}
// Dipanggil saat var_dump($obj) — kontrol output debug
public function __debugInfo(): array
{
return [
'jumlah_items' => count($this->items),
'items' => $this->items,
];
}
}
$koleksi = new Koleksi(['apel', 'mangga']);
echo $koleksi; // "apel, mangga" — via __toString
$koleksi2 = $koleksi('jeruk'); // via __invoke — menambah 'jeruk'
echo $koleksi2; // "apel, mangga, jeruk"
__clone() dan Shallow vs Deep Copy
#
<?php
class Order
{
public array $items = [];
public \DateTime $tanggal;
public function __construct()
{
$this->tanggal = new \DateTime();
}
// Dipanggil saat clone $obj
public function __clone()
{
// Shallow copy: $this->tanggal masih menunjuk ke objek yang sama
// Deep copy: buat instance DateTime baru
$this->tanggal = clone $this->tanggal;
}
}
$order1 = new Order();
$order1->items = ['laptop', 'mouse'];
$order2 = clone $order1; // __clone() dipanggil
$order2->items[] = 'keyboard'; // tidak mempengaruhi order1
var_dump(count($order1->items)); // 2 — tidak berubah
var_dump(count($order2->items)); // 3
Readonly Property (PHP 8.1+) #
Readonly property hanya bisa di-assign satu kali — biasanya di constructor. Setelah itu nilainya tidak bisa diubah, menjadikan object immutable secara parsial:
<?php
class DineroTransfer
{
public function __construct(
public readonly string $id,
public readonly float $jumlah,
public readonly string $dari,
public readonly string $ke,
public readonly \DateTime $dibuat,
) {}
}
$transfer = new DineroTransfer(
id: 'TRF-001',
jumlah: 500_000,
dari: 'BCA-111',
ke: 'Mandiri-222',
dibuat: new \DateTime(),
);
echo $transfer->id; // TRF-001
echo $transfer->jumlah; // 500000
// $transfer->jumlah = 0; // Fatal Error: Cannot modify readonly property
Readonly Class (PHP 8.2+) #
Seluruh kelas bisa dijadikan readonly, menjadikan semua property-nya otomatis readonly:
<?php
readonly class Koordinat
{
public function __construct(
public float $lat,
public float $lng,
public float $altitude = 0,
) {}
public function jarakKe(Koordinat $tujuan): float
{
// Haversine formula — perkiraan jarak antara dua titik di bumi
$r = 6371; // radius bumi dalam km
$dLat = deg2rad($tujuan->lat - $this->lat);
$dLng = deg2rad($tujuan->lng - $this->lng);
$a = sin($dLat / 2) ** 2
+ cos(deg2rad($this->lat))
* cos(deg2rad($tujuan->lat))
* sin($dLng / 2) ** 2;
return $r * 2 * atan2(sqrt($a), sqrt(1 - $a));
}
}
$jakarta = new Koordinat(-6.2088, 106.8456);
$surabaya = new Koordinat(-7.2575, 112.7521);
echo round($jakarta->jarakKe($surabaya)) . " km"; // ~666 km
Anonymous Class #
Anonymous class adalah kelas tanpa nama yang dibuat dan di-instantiate sekaligus. Berguna untuk membuat implementasi sederhana dari interface atau kelas abstrak, terutama dalam konteks testing:
<?php
interface Logger
{
public function log(string $pesan): void;
}
// Anonymous class sebagai implementasi sederhana
function prosesData(array $data, Logger $logger): void
{
$logger->log("Memproses " . count($data) . " item");
// proses data...
$logger->log("Selesai");
}
// Di production — gunakan implementasi nyata
prosesData($data, new FileLogger('/var/log/app.log'));
// Di testing — anonymous class untuk mock cepat
$logBuffer = [];
prosesData($data, new class($logBuffer) implements Logger {
public function __construct(private array &$buffer) {}
public function log(string $pesan): void
{
$this->buffer[] = $pesan;
}
});
// Cek apakah log yang benar ditulis
assert($logBuffer[0] === "Memproses 5 item");
Ringkasan #
- Constructor promotion (PHP 8.0+) menghilangkan boilerplate deklarasi property — cukup tambahkan visibility modifier pada parameter constructor.
- Visibility (
public,protected,private) bukan sekadar akses kontrol — ini tentang menjaga invariant objek agar data selalu konsisten dan valid.- Enkapsulasi yang baik menyembunyikan implementasi dan hanya mengekspos API yang diperlukan. Ubah state internal hanya melalui method yang memvalidasi input.
- Pewarisan tepat untuk relasi “adalah-sebuah” (Mobil adalah Kendaraan). Untuk relasi “memiliki” atau “bisa”, gunakan komposisi atau interface.
parent::__construct()harus dipanggil di constructor kelas anak ketika kelas induk memiliki constructor yang perlu dijalankan.- Abstract class memaksa kelas anak mengimplementasikan method tertentu, sambil menyediakan implementasi bersama untuk method lain.
finalpada kelas mencegah pewarisan, pada method mencegah override — gunakan untuk API yang stabil dan tidak boleh diubah perilakunya.static::vsself::— gunakanstatic::untuk late static binding (merujuk kelas aktual saat runtime),self::untuk merujuk kelas yang mendefinisikan method.- Readonly property (PHP 8.1+) dan readonly class (PHP 8.2+) membuat objek immutable — nilai hanya bisa diset sekali di constructor, tidak bisa diubah setelahnya.
- Anonymous class berguna untuk implementasi interface sederhana, terutama dalam testing sebagai pengganti mock library yang berat.