Semakin besar aplikasi, semakin sering kita menemukan class yang saling mirip: Admin pada dasarnya adalah Pengguna dengan hak tambahan, dan SepedaMotor adalah Kendaraan dengan perilaku khusus. Menulis setiap class dari nol berarti menduplikasi property dan method yang sama berulang-ulang, dan setiap perbaikan harus disalin ke banyak tempat. Inheritance (pewarisan) menyelesaikan masalah ini: satu class mewarisi seluruh member public dan protected milik class lain, lalu hanya menambahkan bagian yang benar-benar berbeda. Pada chapter ini kita akan mempelajari kata kunci extends, peran protected di dalam hierarki, cara merantai constructor, batasan single inheritance, dan kapan pewarisan sebaiknya tidak dipakai.
Mewarisi Class dengan extends #
Kata kunci extends mendeklarasikan bahwa sebuah class adalah child class (subclass) dari class lain yang disebut parent class (superclass). Semua property dan method public serta protected milik parent otomatis tersedia pada child, seolah-olah kode tersebut ditulis ulang di dalam child. Berikut contoh paling sederhananya:
<?php
class Pengguna
{
private string $nama;
public function __construct(string $nama)
{
$this->nama = $nama;
}
public function salam(): string
{
return "Halo, saya {$this->nama}.";
}
}
class Admin extends Pengguna
{
}
$admin = new Admin("Budi");
echo $admin->salam() . "\n";
echo get_class($admin) . "\n";Output yang Dihasilkan:
Halo, saya Budi.
AdminMari kita telaah kode di atas:
- Class
Penggunamenyimpan nama dalam property$namadan menyediakan constructor serta methodsalam(). class Admin extends PenggunamendeklarasikanAdminsebagai child dariPengguna; isi class tersebut sengaja dibiarkan kosong.new Admin("Budi")berhasil karena constructor milikPenggunaikut diwarisi, sehingga cara membuat object-nya sama persis.- Dua baris
echomemanggil method warisansalam(), laluget_class()mengonfirmasi bahwa object tersebut tetap bertipeAdmin.
Class Admin tidak menulis satu baris kode pun, tetapi sudah bisa menyapa layaknya Pengguna. Inilah inti penggunaan ulang kode (code reuse) melalui pewarisan: ketika salam() diperbaiki di Pengguna, semua child otomatis menerima perbaikan yang sama tanpa disentuh.
Relasi is-a dan Penambahan Member Baru #
Pewarisan hanya tepat bila child memenuhi relasi is-a: Admin adalah Pengguna, SepedaMotor adalah Kendaraan. Jika kalimat “… adalah …” terdengar janggal — misalnya “Mobil adalah Mesin” — pewarisan kemungkinan besar merupakan salah desain. Selain mewarisi, child juga boleh menambah member baru yang tidak dimiliki parent:
<?php
class Pengguna
{
protected string $nama;
public function __construct(string $nama)
{
$this->nama = $nama;
}
public function salam(): string
{
return "Halo, saya {$this->nama}.";
}
}
class Editor extends Pengguna
{
public function suntingArtikel(string $judul): string
{
return "{$this->nama} menyunting artikel \"{$judul}\".";
}
}
$editor = new Editor("Siti");
echo $editor->salam() . "\n";
echo $editor->suntingArtikel("Panduan PHP 8") . "\n";Output yang Dihasilkan:
Halo, saya Siti.
Siti menyunting artikel "Panduan PHP 8".Class Editor mewarisi salam() sekaligus membaca property $nama — hal ini dimungkinkan karena $nama kini dideklarasikan protected, bukan private. Method baru suntingArtikel() hanya dimiliki Editor; object Pengguna biasa tidak memilikinya. Dengan pola ini hierarki terbentuk alami: makin ke bawah makin spesifik, makin ke atas makin umum.
instanceof Menelusuri Rantai Hierarki #
Operator instanceof bekerja selaras dengan pewarisan: pemeriksaan tidak berhenti di class langsung, melainkan menelusuri seluruh rantai ancestor. Object bertipe KucingPersia sekaligus dianggap sebagai Kucing dan Hewan:
<?php
class Hewan
{
}
class Kucing extends Hewan
{
}
class KucingPersia extends Kucing
{
}
$persia = new KucingPersia();
var_dump($persia instanceof KucingPersia);
var_dump($persia instanceof Kucing);
var_dump($persia instanceof Hewan);
$kucingBiasa = new Kucing();
var_dump($kucingBiasa instanceof KucingPersia);Output yang Dihasilkan:
bool(true)
bool(true)
bool(true)
bool(false)Empat baris hasil menunjukkan aturannya: instanceof mengembalikan true untuk class object itu sendiri dan seluruh ancestor-nya, tetapi false untuk arah sebaliknya — Kucing bukanlah KucingPersia. Perilaku ini menjadi fondasi fleksibilitas OOP: fungsi yang menerima parameter bertipe Hewan boleh diberi object KucingPersia tanpa error, karena KucingPersia dipastikan membawa seluruh kemampuan Hewan.
protected pada Praktiknya #
Chapter Visibility
memperkenalkan tiga tingkatan akses: public terbuka dari mana saja, private tertutup untuk class itu sendiri, dan protected terbuka untuk class itu beserta seluruh child-nya. Pewarisanlah panggung utama protected — parent menyembunyikan detail dari dunia luar sambil membukanya bagi turunannya:
<?php
class Produk
{
protected int $harga;
public function __construct(int $harga)
{
$this->harga = $harga;
}
protected function formatHarga(): string
{
return "Rp" . number_format($this->harga, 0, ",", ".");
}
}
class Buku extends Produk
{
public function infoHarga(): string
{
// Method protected milik parent bisa dipanggil dari dalam child.
return "Harga buku: " . $this->formatHarga();
}
}
$buku = new Buku(95000);
echo $buku->infoHarga() . "\n";Output yang Dihasilkan:
Harga buku: Rp95.000Method formatHarga() dan property $harga sama-sama protected di Produk. Class Buku bebas mengakses keduanya dari dalam method infoHarga(), sehingga detail format rupiah dapat dipakai ulang tanpa diekspos ke kode luar. Batas ini ditegakkan sungguh-sungguh — kode di luar hierarki ditolak keras:
<?php
class Produk
{
protected int $harga = 95000;
}
$produk = new Produk();
echo $produk->harga . "\n";Output yang Dihasilkan:
Fatal error: Uncaught Error: Cannot access protected property Produk::$harga in /home/z/my-project/php-course/content/php/object-oriented-programming/inheritance/.verify_tmp.php:8
Stack trace:
#0 {main}
thrown in /home/z/my-project/php-course/content/php/object-oriented-programming/inheritance/.verify_tmp.php on line 8PHP menghentikan program dengan Fatal error karena baris $produk->harga berada di skrip utama, bukan di dalam Produk maupun child-nya. Pesan Cannot access protected property menunjuk properti yang dilanggar beserta lokasi persisnya, sehingga perbaikan mudah ditelusuri.
Merantai Constructor dengan parent::__construct #
Ada satu jebakan umum: child yang mendefinisikan constructor sendiri tidak otomatis menjalankan constructor parent, karena PHP tidak pernah memanggil keduanya secara diam-diam. Jika constructor parent berisi inisialisasi penting, child harus memanggilnya secara eksplisit:
<?php
class Pegawai
{
protected string $nama;
protected int $gajiPokok;
public function __construct(string $nama, int $gajiPokok)
{
echo "Constructor Pegawai dijalankan\n";
$this->nama = $nama;
$this->gajiPokok = $gajiPokok;
}
}
class Manajer extends Pegawai
{
protected int $tunjangan;
public function __construct(string $nama, int $gajiPokok, int $tunjangan)
{
echo "Constructor Manajer dijalankan\n";
parent::__construct($nama, $gajiPokok);
$this->tunjangan = $tunjangan;
}
public function gajiTotal(): int
{
return $this->gajiPokok + $this->tunjangan;
}
}
$manajer = new Manajer("Budi", 8000000, 2500000);
echo "Gaji total: Rp" . $manajer->gajiTotal() . "\n";Output yang Dihasilkan:
Constructor Manajer dijalankan
Constructor Pegawai dijalankan
Gaji total: Rp10500000Urutan teks yang tercetak membongkar urutan eksekusinya. Constructor Manajer berjalan lebih dulu dan segera memanggil parent::__construct($nama, $gajiPokok) — pada titik ini giliran constructor Pengawai mengisi $nama dan $gajiPokok. Setelah kembali ke constructor Manajer, property $tunjangan milik child barulah diisi. Tanpa baris parent::__construct(...) tersebut, $nama dan $gajiPokok tidak pernah diinisialisasi, dan gajiTotal() akan error saat membaca property yang belum bernilai.
PHP Hanya Mendukung Single Inheritance #
Berbeda dengan bahasa seperti C++, PHP tidak mengenal multiple inheritance: satu class hanya boleh memiliki satu parent. Mencoba mendaftarkan dua parent sekaligus ditolak bahkan sebelum program berjalan:
<?php
class Burung
{
}
class Pesawat
{
}
class BurungPesawat extends Burung, Pesawat
{
}Output yang Dihasilkan:
Parse error: syntax error, unexpected token ",", expecting "{" in /home/z/my-project/php-course/content/php/object-oriented-programming/inheritance/.verify_tmp.php on line 10Parser menolak koma pada extends Burung, Pesawat dengan Parse error — program gagal dikompilasi sejak awal, bukan sekadar menerima peringatan. Kebutuhan berbagi perilaku lintas cabang hierarki tetap dapat dipenuhi lewat interface dan trait, dua mekanisme yang akan kita bahas pada chapter-chapter selanjutnya di section ini.
Kapan Sebaiknya Tidak Mewarisi #
Pewarisan yang dipaksakan pada relasi has-a justru menciptakan hierarki kaku dan rapuh. Mobil memiliki (has-a) mesin, jadi Mobil tidak seharusnya diwariskan dari Mesin — yang tepat adalah menyimpan object Mesin di dalam property, teknik yang disebut composition:
<?php
class Mesin
{
private string $jenisBahanBakar;
public function __construct(string $jenisBahanBakar)
{
$this->jenisBahanBakar = $jenisBahanBakar;
}
public function nyalakan(): string
{
return "Mesin {$this->jenisBahanBakar} menyala.";
}
}
class Mobil
{
private Mesin $mesin;
public function __construct(string $jenisBahanBakar)
{
$this->mesin = new Mesin($jenisBahanBakar);
}
public function start(): string
{
return $this->mesin->nyalakan() . " Mobil siap melaju.";
}
}
$mobil = new Mobil("bensin");
echo $mobil->start() . "\n";Output yang Dihasilkan:
Mesin bensin menyala. Mobil siap melaju.Membuat Mobil sekaligus membangun Mesin di dalamnya, lalu Mobil cukup mendelegasikan pekerjaan menyalakan kepada object tersebut. Desain seperti ini lebih mudah diubah: jenis mesin bisa diganti tanpa membongkar hierarki class. Chapter Object Composition
akan membedah pertimbangan ini lebih dalam; untuk sekarang, cukup pegang prinsipnya — warisi untuk relasi is-a, gabungkan (compose) untuk relasi has-a.
Ringkasan #
extends membuat child mewarisi seluruh member public dan protected milik parent, termasuk constructor bila child tidak mendefinisikan sendiri. Member protected menjadi jembatan di antara generasi: tersembunyi dari luar, terbuka bagi turunan. Operator instanceof mengikuti seluruh rantai hierarki sehingga child selalu bisa dipakai di tempat parent diharapkan. Sementara itu, PHP membatasi pewarisan hanya satu parent, dan constructor parent harus dipanggil manual lewat parent::__construct(). Dengan hierarki yang kini bisa diwarisi, pertanyaan berikutnya muncul alami: bagaimana bila child ingin mengubah perilaku method warisan? Method overriding adalah topik chapter berikutnya.