Chapter Parameter Types pada Section Fungsi telah membahas mekanikanya sehari-hari: sintaks deklarasi, koersi nilai, nilai default, hingga parameter variadic. Chapter ini mengangkat sudut pandang yang berbeda — parameter type sebagai bagian dari sistem tipe yang lebih besar. Pertanyaannya bukan lagi “bagaimana menulisnya”, melainkan “apa yang terjadi pada kontrak ini ketika inheritance masuk ke dalam gambar” dan “bagaimana alat static analysis membacanya”. Dua pertanyaan tersebut menentukan apakah hierarki class Anda tetap sehat atau justru menyimpan jebakan signature.

Parameter Types sebagai Kontrak Masukan #

Setiap deklarasi tipe pada parameter adalah janji dua arah. Pemanggil berjanji hanya mengirim nilai yang cocok; sebagai imbalannya, isi fungsi boleh berasumsi nilai tersebut sudah bersih tanpa memeriksa ulang. Ketika sebuah child class menimpa method, janji itu ikut diwarisi: semua kode lama yang sah memanggil parent harus tetap sah memanggil child. Dari syarat itulah lahir aturan variance — dan untuk parameter, arahnya mengejutkan bagi pendatang dari bahasa lain: anak hanya boleh menerima lebih banyak, bukan lebih sedikit.

Contravariance — Anak Boleh Melebarkan Parameter #

Sejak PHP 7.4, parameter pada method anak boleh dilebarkan (widening) ke tipe yang lebih umum. Arah pembesaran ini disebut contravariance. Contoh berikut memperlihatkan pola klasiknya:

php
<?php
interface Hewan
{
    public function nama(): string;
}

class Kucing implements Hewan
{
    public function nama(): string { return "Kucing"; }
}

class Anjing implements Hewan
{
    public function nama(): string { return "Anjing"; }
}

abstract class PenjagaHewan
{
    abstract public function rawat(Kucing $hewan): void;
}

class PenjagaUmum extends PenjagaHewan
{
    // Melebarkan parameter Kucing → Hewan (contravariance, sah sejak PHP 7.4)
    public function rawat(Hewan $hewan): void
    {
        echo "Merawat: " . $hewan->nama() . "\n";
    }
}

$penjaga = new PenjagaUmum();
$penjaga->rawat(new Kucing());
$penjaga->rawat(new Anjing());

Output yang Dihasilkan:

text
Merawat: Kucing
Merawat: Anjing

Mari kita bedah kontrak yang terbentuk:

  1. Kontrak parent — PenjagaHewan::rawat() berjanji menerima Kucing; seluruh kode yang memanggil method abstrak ini menyiapkan diri mengirim kucing.
  2. Pelebaran di anak — PenjagaUmum::rawat() menerima Hewan, tipe yang lebih umum dan memuat Kucing; setiap panggilan yang sah bagi parent otomatis tetap sah bagi child.
  3. Kemampuan baru — pemanggil $penjaga->rawat(new Anjing()) kini diterima karena Anjing juga Hewan; jangkauan method melebar tanpa memutus satu pun pemanggil lama.

Alasan keamanannya sederhana: menerima lebih banyak nilai tidak pernah membuat pemanggil lama gagal. Kebalikannya berbahaya — andaikata anak mempersempit parameter menjadi Anjing, pemanggil yang selama ini sah mengirim Kucing akan ditolak, dan janji parent patah di tangan anak.

Melebarkan Diperbolehkan, Menyempitkan Ditolak #

Aturan arah untuk parameter dirangkum dalam tabel berikut:

Perubahan tipe parameter di kelas anakIstilahStatus
int → int|float (melebarkan)widening (contravariance)Sah
Hewan → Kucing (menyempitkan)narrowingFatal error
Menghapus type declarationwidening penuhSah, tetapi kontrak memburuk
Menambah parameter wajib baru—Fatal error
Menambah parameter opsional bernilai default—Sah
Mengubah parameter by-value menjadi by-reference—Fatal error

Contoh pelebaran dalam praktik sering memakai union type:

php
<?php
class Pajak
{
    public function hitung(int $jumlah): float
    {
        return $jumlah * 0.11;
    }
}

class PajakBerlapis extends Pajak
{
    // Melebarkan parameter int → int|float (contravariance)
    public function hitung(int|float $jumlah): float
    {
        return $jumlah * 0.11;
    }
}

$pajak = new PajakBerlapis();
echo $pajak->hitung(200000) . "\n";
echo $pajak->hitung(150000.5) . "\n";

Output yang Dihasilkan:

text
22000
16500.055

Dua pemanggilan pada contoh tersebut menunjukkan kontrak yang melebar:

  1. Panggilan lama tetap sah — hitung(200000) mengirim int persis seperti syarat parent; hasilnya 22000.
  2. Panggilan baru diterima — hitung(150000.5) memanfaatkan pelebaran ke int|float; pada parent, argumen float ini akan memicu TypeError dalam mode ketat.

PHP 8 Menolak Signature yang Tidak Kompatibel #

Sebelum PHP 8.0, signature yang bertentangan antara parent dan child hanya menghasilkan peringatan; program tetap berjalan dengan perilaku tak terduga. PHP 8 mengubahnya menjadi fatal error yang muncul saat deklarasi class diproses — jauh sebelum method sempat dipanggil. Contoh berikut sengaja menyempitkan tipe parameter dan berhenti dengan exit code 255:

php
<?php
class Pengirim
{
    public function kirim(string $pesan): void
    {
        echo "Mengirim: $pesan\n";
    }
}

class PengirimDarurat extends Pengirim
{
    // Menyempitkan parameter string → int: tidak kompatibel
    public function kirim(int $pesan): void
    {
        echo "Mengirim darurat: $pesan\n";
    }
}

echo "Baris ini tidak pernah tereksekusi.\n";

Output yang Dihasilkan:

text

Fatal error: Declaration of PengirimDarurat::kirim(int $pesan): void must be compatible with Pengirim::kirim(string $pesan): void in /home/z/my-project/php-course/content/php/type-system-dan-static-analysis/parameter-types/.verify_tmp.php on line 13

PHP menolak PengirimDarurat begitu deklarasi class tersebut diproses: pesan must be compatible with menampilkan kedua signature bersebelahan sehingga dapat langsung dibandingkan. Baris echo di paling bawah tidak pernah tereksekusi. Aturan lengkap kompatibilitas signature — jumlah parameter, by-reference, hingga visibility — dibahas pada chapter Method Overriding ; fokus kita di sini adalah sisi tipenya.

Tentative Return Type pada Method Bawaan #

Sekilas mengenai method bawaan PHP: sejak versi 8.1, ratusan method internal diberi return type secara bertahap (tentative return type). Signature lama Anda yang tidak lagi cocok tidak langsung fatal — PHP menerbitkan deprecation terlebih dahulu untuk memberi waktu beradaptasi, dan atribut #[\ReturnTypeWillChange] dapat menekan deprecation itu sementara. Meski mekanismenya berada di sisi return type, pelajarannya untuk parameter tetap berlaku: samakan signature Anda dengan deklarasi interface bawaan selangkap mungkin. Kisah lengkapnya dibedah pada chapter Return Types .

Parameter Types di Mata Static Analysis #

Bagi static analysis, setiap parameter type adalah fakta yang harus diverifikasi di semua titik panggilan. PHPStan dan Psalm akan menandai panggilan yang mengirim nilai bertipe salah — tanpa perlu menjalankan program sedikit pun. Untuk informasi yang tidak bisa diungkap deklarasi native, anotasi @param melengkapi kontraknya:

php
<?php
/**
 * @param array<int, string> $baris daftar nama produk
 */
function tampilkanDaftar(array $baris): void
{
    foreach ($baris as $index => $nama) {
        echo ($index + 1) . ". " . $nama . "\n";
    }
}

tampilkanDaftar(["Kopi Gayo", "Teh Melati"]);

Output yang Dihasilkan:

text
1. Kopi Gayo
2. Teh Melati

Pembagian perannya sebagai berikut:

  1. Tipe native — array $baris menjamin argumen berupa array; pelanggaran tertangkap runtime maupun static analysis.
  2. Anotasi @param — array<int, string> menegaskan isi array: kunci int (indeks) dan nilai string (nama produk); static analysis memakainya untuk menolak tampilkanDaftar([42]) sebelum runtime.
  3. Isi fungsi — foreach mengiterasi pasangan kunci-nilai; $index + 1 menghasilkan nomor urut berbasis satu sesuai kontrak bentuk data.

PHPStan sendiri adalah paket Composer yang dijalankan di proyek Anda — alat ini tidak terpasang di lingkungan buku ini, sehingga blok perintah berikut tidak dieksekusi otomatis:

bash
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src --level=6

Dengan --level=6, PHPStan memeriksa tipe yang hilang dan panggilan yang tidak konsisten — termasuk pelanggaran kontrak parameter. Pemasangan, pemilihan level, dan cara membaca temuan dibahas tuntas pada chapter Menggunakan PHPStan .

Referensi #

Ringkasan #

Parameter type adalah janji masukan yang diwarisi turun-temurun: anak boleh melebarkannya (contravariance, sah sejak PHP 7.4) tetapi tidak boleh menyempitkannya — pelanggaran arah maupun bentuk signature ditolak PHP 8 dengan fatal error saat deklarasi class. Di sisi alat, deklarasi native dan anotasi @param menjadi kontrak yang diverifikasi static analysis di setiap titik panggilan. Dengan kontrak masukan terjaga, chapter berikutnya membalik arah pandang ke sisi keluaran: return types, tempat aturan variance berputar ke arah sebaliknya — menyempit justru dianjurkan.