Sepanjang Section Tipe Data dan Type System kita telah menulis banyak type declaration: int untuk harga, array untuk daftar item, ?string untuk nilai yang boleh kosong, hingga union dan intersection types. Setiap deklarasi adalah kontrak antara fungsi dan pemanggilnya, dan runtime PHP benar-benar menegakkan kontrak tersebut. Penegaknya bernama TypeError — error yang dilempar ketika nilai yang lewat tidak cocok dengan tipe yang dijanjikan. Pada chapter ini kita akan membedah anatomi pesannya, membandingkan penegakan pada mode weak dan strict, mencermati satu deprecation penting di PHP 8.4, lalu belajar menangkap TypeError dan membaca stack trace dengan tenang.
Anatomi Pesan TypeError #
Mari mulai dari pelanggaran yang paling sering terjadi: mengirim argumen dengan tipe yang tidak sesuai. Fungsi hitungPajak() di bawah menjanjikan parameter $harga bertipe int, tetapi pemanggil mengirim string "lima ratus" — teks yang mustahil dikonversi menjadi angka. Simpan kode berikut sebagai pajak.php lalu jalankan:
<?php
function hitungPajak(int $harga): int
{
return intdiv($harga, 10);
}
echo hitungPajak("lima ratus");Output yang Dihasilkan:
Fatal error: Uncaught TypeError: hitungPajak(): Argument #1 ($harga) must be of type int, string given, called in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php on line 7 and defined in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php:2
Stack trace:
#0 /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php(7): hitungPajak('lima ratus')
#1 {main}
thrown in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php on line 2Kodenya singkat, tetapi pesan errornya panjang dan padat informasi. Mari kita pecah satu per satu:
hitungPajak():— nama fungsi yang kontraknya dilanggar.Argument #1 ($harga)— nomor posisi dan nama parameter yang bermasalah.must be of type int— tipe yang dijanjikan pada deklarasi.string given— tipe aktual yang dikirim pemanggil.called in ... on line 7— berkas dan baris tempat pemanggilan terjadi.and defined in ...:2— lokasi deklarasi fungsi.
Karena tidak ada satu pun catch yang menangkapnya, error ini bersifat fatal: eksekusi berhenti total dan PHP keluar dengan exit code 255. Anda telah berkenalan dengan tingkatan error PHP pada Chapter Memahami PHP Errors
; TypeError adalah anggota keluarga Error yang muncul khusus untuk pelanggaran tipe.
Dua Mode Penegakan: Weak dan Strict #
PHP memiliki dua mode penegakan tipe yang telah Anda coba pada Chapter Strict Types : mode weak (default) dan mode strict. Pada mode weak, PHP berusaha mengonversi nilai masukan secara otomatis sebelum memeriksa kontraknya — kelanjutan dari perilaku type juggling yang kita bahas awal section ini. Lihatlah dua pemanggilan berikut terhadap fungsi yang sama:
<?php
function hitungPajak(int $harga): int
{
return intdiv($harga, 10);
}
var_dump(hitungPajak("500000"));
var_dump(hitungPajak(150000.75));Output yang Dihasilkan:
int(50000)
Deprecated: Implicit conversion from float 150000.75 to int loses precision in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php on line 2
int(15000)Kedua pemanggilan di atas lolos dari TypeError, tetapi dengan cerita yang berbeda. String "500000" adalah numeric string sehingga dikonversi mulus menjadi int 500000, lalu intdiv() menghasilkan 50000. Nilai float 150000.75 juga dipotong menjadi 15000, tetapi PHP menandai konversi yang kehilangan presisi dengan Deprecated — program tetap berjalan, sinyal tersebut hanya mengingatkan bahwa pemotongan angka rawan menjadi sumber bug. Perhatikan pula bahwa pesan deprecation menunjuk baris 2, yaitu baris deklarasi fungsi, karena konversi terjadi tepat saat argumen masuk ke dalam fungsi.
Rangkuman perilaku ketiga jenis masukan pada kedua mode:
| Nilai yang dikirim | Mode weak (default) | Mode strict |
|---|---|---|
"500000" (numeric string) | Dikonversi menjadi int | TypeError |
150000.75 (float) | Dipotong menjadi int, disertai Deprecated | TypeError |
"lima ratus" (string non-numerik) | TypeError | TypeError |
Pada mode strict — diaktifkan dengan declare(strict_types=1) — PHP menolak semua konversi implisit. String "500000" yang tadi diloloskan kini ditolak mentah-mentah:
<?php
declare(strict_types=1);
function hitungPajak(int $harga): int
{
return intdiv($harga, 10);
}
var_dump(hitungPajak("500000"));Output yang Dihasilkan:
Fatal error: Uncaught TypeError: hitungPajak(): Argument #1 ($harga) must be of type int, string given, called in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php on line 9 and defined in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php:4
Stack trace:
#0 /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php(9): hitungPajak('500000')
#1 {main}
thrown in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php on line 4Format pesannya persis sama dengan anatomi yang kita bedah di awal; yang berubah hanyalah jenis pelanggarannya — kini pelanggarannya adalah mengirim string untuk parameter int, titik. Ingat dua aturan penting strict mode: declare(strict_types=1) wajib menjadi statement pertama dalam berkas, dan aturannya mengikat panggilan yang ditulis di berkas tersebut — bukan berkas tempat fungsi didefinisikan.
Pelanggaran pada Nilai Kembali #
Parameter bukan satu-satunya kontrak yang dijaga; nilai kembali fungsi juga diawasi dengan format pesan yang berbeda. Fungsi totalHarga() berikut menjanjikan nilai kembali int, tetapi pernyataan return-nya mengembalikan string berformat rupiah:
<?php
function totalHarga(array $items): int
{
$total = 0;
foreach ($items as $harga) {
$total += $harga;
}
return "Rp" . number_format($total, 0, ",", ".");
}
echo totalHarga(["Buku" => 75000, "Pena" => 12000]);Output yang Dihasilkan:
Fatal error: Uncaught TypeError: totalHarga(): Return value must be of type int, string returned in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php:9
Stack trace:
#0 /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php(12): totalHarga(Array)
#1 {main}
thrown in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php on line 9Bedakan dua frasa kuncinya. Return value must be of type int, string returned menyatakan bahwa pelanggaran terjadi pada nilai kembali, dan penunjuk in ...:9 mengarah ke baris pernyataan return yang melanggar — bukan ke baris pemanggilan seperti pada kasus parameter. Frame #0 kemudian melengkapi informasi siapa pemanggilnya: skrip utama pada baris 12. Bug seperti ini biasanya lahir dari fungsi yang mencampur adukan tanggung jawab menghitung dan memformat; solusinya adalah memisahkan pemformatan "Rp" ke pemanggil, atau mengubah deklarasi nilai kembali menjadi string.
Parameter Implicitly Nullable di PHP 8.4 #
Tidak semua masalah tipe datang sebagai TypeError; kadang PHP lebih dulu mengirim sinyal lewat Deprecated. Salah satunya adalah parameter implicitly nullable, pola lama yang resmi ditandai terdepresiasi pada PHP 8.4:
<?php
function sapaPengguna(string $nama = null): string
{
return $nama === null ? "Halo, tamu!" : "Halo, $nama!";
}
echo sapaPengguna(), "\n";
echo sapaPengguna("Budi"), "\n";Output yang Dihasilkan:
Deprecated: sapaPengguna(): Implicitly marking parameter $nama as nullable is deprecated, the explicit nullable type must be used instead in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php on line 2
Halo, tamu!
Halo, Budi!Fungsi sapaPengguna() menulis string $nama = null — nilai bawaan null pada parameter yang tipenya tidak menyertakan ?. Selama bertahun-tahun PHP menoleransi pola ini dengan menganggap parameter tersebut implisit nullable, dan PHP 8.4 resmi menandainya Deprecated. Perhatikan bahwa deprecation bukan error: skrip tetap berjalan, kedua baris echo tetap tercetak, dan pemeriksaan $nama === null tetap bekerja dengan benar. Status Deprecated berarti perilaku tersebut akan dihapus di versi PHP mendatang, sehingga perbaiki sekarang dengan menuliskan nullable type secara eksplisit:
<?php
function sapaPengguna(?string $nama = null): string
{
return $nama === null ? "Halo, tamu!" : "Halo, $nama!";
}Tanda tanya pada ?string menyatakan secara eksplisit bahwa parameter menerima string maupun null — materi lengkapnya telah kita bahas pada Chapter Nullable Types
.
Membaca Stack Trace #
Semakin dalam fungsi bersarang, semakin panjang stack trace yang menyertai error — dan semakin penting kemampuan membacanya. Program berikut memiliki dua lapis pemanggilan sebelum akhirnya tersandung:
<?php
function formatHarga(float $harga): string
{
return "Rp" . number_format($harga, 0, ",", ".");
}
function tampilkanKeranjang(array $keranjang): void
{
foreach ($keranjang as $nama => $harga) {
echo $nama . ": " . formatHarga($harga) . "\n";
}
}
tampilkanKeranjang(["Buku" => 75000, "Pena" => "gratis"]);Output yang Dihasilkan:
Buku: Rp75.000
Fatal error: Uncaught TypeError: formatHarga(): Argument #1 ($harga) must be of type float, string given, called in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php on line 10 and defined in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php:2
Stack trace:
#0 /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php(10): formatHarga('gratis')
#1 /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php(14): tampilkanKeranjang(Array)
#2 {main}
thrown in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php on line 2Stack trace dibaca mulai dari frame paling atas (#0) karena frame itulah lokasi error terjadi:
#0 ... (10): formatHarga('gratis')— panggilan terdalam:formatHarga()dipanggil dengan string'gratis'pada baris 10, dan konversi argumen tersebut kefloatgagal.#1 ... (14): tampilkanKeranjang(Array)— pemanggilnya:tampilkanKeranjang()sedang berjalan pada baris 14 saat error terjadi.#2 {main}— akar alur: skrip utama.thrown in ... on line 2— titik lempar sesungguhnya: baris deklarasiformatHarga().
Dengan membaca dari #2 turun ke #0, kita merekonstruksi alur kejadian: skrip utama memanggil tampilkanKeranjang(), yang memanggil formatHarga(), yang menolak string 'gratis'. Nilai argumen yang bermasalah ('gratis') tercetak langsung pada frame — informasi berharga saat mencari asal data yang salah. Di situlah kegunaan stack trace: ia memangkas waktu diagnosis dari “program error entah di mana” menjadi “data ini masuk lewat jalur ini”.
Menangkap TypeError #
Error fatal yang tidak tertangkap menghentikan seluruh skrip — tidak ideal ketika satu data buruk tidak seharusnya mematikan proses yang lain. Solusinya adalah try-catch, tetapi ada satu fakta hierarki yang wajib dipahami lebih dulu:
Throwable (interface)
├── Error
│ ├── TypeError
│ ├── ValueError
│ └── ArgumentCountError
└── Exception
├── RuntimeException
└── LogicExceptionTypeError berada di bawah kelas Error, bukan Exception. Keduanya sama-sama mengimplementasikan interface Throwable, sehingga TypeError dapat ditangkap dengan catch (TypeError), catch (Error), maupun catch (Throwable).
Sekarang kita terapkan. Contoh berikut memproses daftar produk yang datanya mungkin tidak bersih; setiap pemanggilan hitungDiskon() dibungkus try-catch sehingga produk bermasalah cukup dilewati tanpa mematikan loop:
<?php
function hitungDiskon(int $harga, int $persen): int
{
return intdiv($harga * $persen, 100);
}
$produk = [
["Buku", 75000, 10],
["Pena", "gratis", 10],
];
foreach ($produk as $item) {
try {
echo $item[0] . ": " . hitungDiskon($item[1], $item[2]) . "\n";
} catch (TypeError $e) {
echo $item[0] . ": dilewati - " . $e->getMessage() . "\n";
}
}Output yang Dihasilkan:
Buku: 7500
Pena: dilewati - hitungDiskon(): Argument #1 ($harga) must be of type int, string given, called in /home/z/my-project/php-course/content/php/tipe-data-dan-type-system/memahami-type-errors/.verify_tmp.php on line 14Baris pertama tercetak normal karena 75000 cocok dengan kontrak int. Ketika giliran "gratis" tiba, TypeError dilempar — tetapi kali ini tertangkap, sehingga loop berlanjut ke baris berikutnya. Pesan lengkap dari $e->getMessage() tetap tampil termasuk lokasi pemanggilan, sangat membantu bila dicatat ke log; hanya akhiran and defined in yang tidak ikut pada versi tertangkap ini. Gunakan pola seperti ini di batas sistem — tempat data dari luar (form, API, berkas) masuk ke fungsi bertipe ketat — lalu catat dan putuskan tindakannya. Jangan pernah menelan error begitu saja tanpa meninggalkan jejak, karena data yang buruk perlu diketahui, bukan disembunyikan.
Ringkasan #
TypeError adalah penegak kontrak tipe PHP: pesannya menyebut fungsi, posisi parameter, tipe yang dijanjikan, tipe yang dikirim, lokasi pemanggilan, dan lokasi deklarasi — sementara pelanggaran nilai kembali dilaporkan dengan frasa Return value must be of type. Mode weak masih mengonversi nilai yang memungkinkan (dengan Deprecated untuk konversi yang kehilangan presisi), mode strict menolak konversi apa pun, dan parameter implicitly nullable kini berstatus Deprecated di PHP 8.4. Karena TypeError berada di bawah Throwable, error tersebut dapat ditangkap di batas sistem, dan stack trace membantu merekonstruksi alur pemanggilan sampai ke sumber data. Dengan type system beserta penegaknya kini tuntas, kita lanjut ke Section Operator dan Expression: bagaimana nilai-nilai bertipe ini diolah menjadi hasil.