Mewarisi method dari parent itu praktis, tetapi sering kali perilaku warisan tidak sepenuhnya cocok untuk child: Kendaraan memiliki method info() yang generik, sedangkan SepedaMotor membutuhkan versi yang lebih spesifik. Method overriding memungkinkan child mendefinisikan ulang method warisan dengan nama yang sama, sehingga pemanggilan melalui object child menjalankan versi child. Pada chapter ini kita akan mempelajari cara menimpa method dengan benar, aturan kompatibilitas signature yang ditegakkan ketat oleh PHP 8, dan perbedaan istilah overriding dengan overloading yang sering tertukar.

Mengganti Perilaku Method Parent #

Overriding terjadi ketika child mendefinisikan method dengan nama yang sama persis seperti method parent. Mulai saat itu, setiap pemanggilan melalui object child menjalankan versi milik child — versi parent tergantikan untuk object tersebut:

php
<?php
class Kendaraan
{
    protected string $merek;

    public function __construct(string $merek)
    {
        $this->merek = $merek;
    }

    public function info(): string
    {
        return "{$this->merek} adalah kendaraan beroda.";
    }
}

class SepedaMotor extends Kendaraan
{
    public function info(): string
    {
        return "{$this->merek} adalah sepeda motor dua roda.";
    }
}

$motor = new SepedaMotor("Vega");
echo $motor->info() . "\n";

Output yang Dihasilkan:

text
Vega adalah sepeda motor dua roda.

Mari kita bedah contoh di atas:

  1. Kendaraan::info() mengembalikan deskripsi generik yang hanya memanfaatkan $merek.
  2. SepedaMotor::info() memakai nama dan signature yang sama — tanpa parameter, return string — tetapi dengan isi yang lebih spesifik.
  3. $motor->info() menjalankan versi child karena $motor bertipe SepedaMotor.

Signature yang dipertahankan membuat kedua versi dapat dipertukarkan: kode yang hanya mengenal Kendaraan tetap bekerja ketika menerima object SepedaMotor, dan hasilnya otomatis mengikuti versi paling spesifik dari object tersebut.

Melengkapi Perilaku Parent dengan parent:: #

Mengganti total bukan satu-satunya pilihan. Sering kali kita hanya ingin membungkus perilaku parent dengan tambahan di sekelilingnya — misalnya melengkapi notifikasi umum dengan keterangan channel email. Kata kunci parent:: memanggil versi parent dari dalam child:

php
<?php
class Notifikasi
{
    public function kirim(string $pesan): string
    {
        return "Notifikasi umum: {$pesan}";
    }
}

class NotifikasiEmail extends Notifikasi
{
    public function kirim(string $pesan): string
    {
        $hasilDasar = parent::kirim($pesan);
        return "{$hasilDasar} (dikirim ke [email protected])";
    }
}

$notifikasi = new NotifikasiEmail();
echo $notifikasi->kirim("Pesanan Anda sudah dikirim") . "\n";

Output yang Dihasilkan:

text
Notifikasi umum: Pesanan Anda sudah dikirim (dikirim ke [email protected])

Method NotifikasiEmail::kirim() tidak menyalin ulang logika Notifikasi::kirim(); method tersebut memanggil parent::kirim($pesan), menampung hasilnya di $hasilDasar, lalu menambahkan keterangan channel di belakangnya. Pola ini menjaga logika dasar tetap satu sumber — perbaikan pada parent langsung terbawa ke semua child yang memanggilnya. Perhatikan juga bahwa argumen $pesan tetap dilewatkan secara eksplisit, karena parent:: adalah rujukan ke class, bukan object yang membawa parameter otomatis.

Aturan Kompatibilitas Signature #

PHP 8 menegakkan kompatibilitas signature dengan ketat saat overriding method biasa. Child yang melanggar ditolak lewat fatal error pada saat class dideklarasikan, bukan saat method dipanggil. Aturan mainnya:

  • Jumlah parameter wajib tidak boleh bertambah; child hanya boleh menambahkan parameter opsional yang punya nilai default.
  • Tipe parameter boleh sama atau lebih longgar (dilebarkan), tetapi tidak boleh lebih ketat dari milik parent.
  • Tipe return boleh sama atau lebih spesifik (dipersempit), tetapi tidak boleh lebih longgar.
  • Status by-reference parameter harus tetap sama.
  • Tingkat visibility tidak boleh diperlonggar — method public di parent tidak boleh menjadi protected di child.

Contoh berikut melanggar aturan kedua: child mencoba mengganti tipe parameter dari string menjadi int:

php
<?php
class Pembayaran
{
    public function proses(string $nomorInvoice): string
    {
        return "Memproses invoice {$nomorInvoice}";
    }
}

class PembayaranQris extends Pembayaran
{
    // Tipe parameter diubah dari string menjadi int → tidak kompatibel.
    public function proses(int $nomorInvoice): string
    {
        return "QRIS memproses invoice {$nomorInvoice}";
    }
}

echo (new PembayaranQris())->proses("INV-2026-001") . "\n";

Output yang Dihasilkan:

text

Fatal error: Declaration of PembayaranQris::proses(int $nomorInvoice): string must be compatible with Pembayaran::proses(string $nomorInvoice): string in /home/z/my-project/php-course/content/php/object-oriented-programming/method-overriding/.verify_tmp.php on line 13

PHP menolak deklarasi PembayaranQris sejak baris class tersebut dieksekusi: pesan must be compatible with menampilkan kedua signature berdampingan sehingga kita bisa langsung membandingkannya. Program berhenti dengan exit code 255 sebelum pemanggilan proses() sempat berjalan, dan baris echo tidak pernah tereksekusi.

Melebarkan Tipe Parameter #

Arah yang diperbolehkan bagi parameter adalah melebarkan (widening) tipe. Parent yang menerima int bisa ditimpa child yang menerima int|float, karena setiap pemanggilan yang sah bagi parent tetap sah bagi child:

php
<?php
class Kalkulator
{
    public function hitung(int $nilai): int
    {
        return $nilai * 2;
    }
}

class KalkulatorLuas extends Kalkulator
{
    // Parameter dilebarkan dari int menjadi int|float → diperbolehkan.
    public function hitung(int|float $nilai): int
    {
        return (int) round($nilai * 2);
    }
}

$luas = new KalkulatorLuas();
echo $luas->hitung(10) . "\n";
echo $luas->hitung(7.5) . "\n";

Output yang Dihasilkan:

text
20
15

Class KalkulatorLuas melebarkan parameter dari int menjadi int|float, sehingga pemanggilan hitung(7.5) kini sah dan menghasilkan 15. Pemanggilan lama seperti hitung(10) tidak terganggu — kompatibilitas ke belakang tetap terjaga, dan itulah alasan PHP hanya mengizinkan arah ini. Aturan arah tipe tersebut dinamakan contravariance untuk parameter dan covariance untuk return type; keduanya dirinci dalam dokumentasi variance PHP .

Overriding Bukan Overloading #

Istilah overloading sering tertukar dengan overriding. Pada bahasa seperti Java atau C++, overloading berarti beberapa method bernama sama dengan daftar parameter berbeda di dalam satu class, lalu compiler memilih versi yang cocok berdasarkan argumen. PHP tidak memiliki mekanisme tersebut — mendeklarasikan method bernama sama dua kali dalam satu class langsung ditolak:

php
<?php
class Tagihan
{
    public function bayar(int $jumlah): void
    {
        echo "Membayar Rp{$jumlah}\n";
    }

    public function bayar(string $nomorInvoice): void
    {
        echo "Membayar invoice {$nomorInvoice}\n";
    }
}

Output yang Dihasilkan:

text

Fatal error: Cannot redeclare Tagihan::bayar() in /home/z/my-project/php-course/content/php/object-oriented-programming/method-overriding/.verify_tmp.php on line 9

Pesan Cannot redeclare menegaskan bahwa Tagihan hanya boleh memiliki satu method bayar(), berapa pun variasi parameternya. Untuk kebutuhan “parameter berbeda, perilaku berbeda”, PHP menyediakan jalan lain: union type seperti int|string, parameter opsional dengan nilai default, atau cukup method bernama berbeda. PHP sendiri memakai istilah overloading untuk hal yang sepenuhnya berbeda — interceptor __call() dan __get() yang bereaksi terhadap method atau property tak terdefinisi — yang akan kita bahas pada Chapter Magic Methods .

Ringkasan #

Method overriding menimpa perilaku warisan lewat signature yang sama, parent::method() melengkapi alih-alih mengganti, dan PHP 8 menjaga kompatibilitas melalui aturan variance: parameter boleh melebar, return boleh menyempit, dan pelanggaran berujung fatal error saat class dideklarasikan. Sementara itu, PHP tidak mengenal overloading klasik ala Java/C++, sehingga variasi parameter diselesaikan dengan union type atau parameter opsional. Kita kini mampu mengubah perilaku individual di dalam hierarki; chapter berikutnya memperkenalkan tiga kata kunci yang menjadi napas komunikasi antar class dalam pewarisan — parent, self, dan static.

Referensi #