D
P
0
← Semua artikel Read in English

WordPress & PHP di Produksi

`?error=` Raib dari `$_GET`: Jangan Pakai Nama Query Var yang Sudah Dipesan WordPress

· · 4 menit baca
`?error=` Raib dari `$_GET`: Jangan Pakai Nama Query Var yang Sudah Dipesan WordPress

Ada bug yang bikin kamu curiga sama logika sendiri sampai berjam-jam, padahal logikanya benar dari awal. Yang salah cuma satu: nama parameter yang saya pilih ternyata sudah punya pemilik.

Ini kejadian di sebuah situs membership yang saya kerjakan. Alur autentikasi dan pengajuan profilnya pakai pola paling standar di dunia PHP: proses form, redirect balik ke halaman asal, sisipkan ?error=sesuatu di URL, lalu tampilkan notice sesuai isi parameternya. Pola yang sudah saya pakai ratusan kali.

Kali ini notice-nya tidak pernah muncul. Sekali pun.

Gejalanya: URL-nya benar, isinya tidak

Yang bikin bingung, di address bar semuanya kelihatan sempurna. Setelah submit gagal, browser mendarat di URL seperti ini:

/request-profile/?error=exists&match=x&type=company

Parameternya ada, ejaannya benar, redirect-nya jalan. Tapi halamannya polos. Tidak ada notice, tidak ada pesan merah, tidak ada apa pun yang memberi tahu user kenapa pengajuannya ditolak.

Reaksi pertama saya jelas salah alamat: saya mulai mencurigai template, kondisi if, urutan hook, sampai caching. Saya sempat menyalahkan full page cache karena dari luar memang mirip gejalanya.

Halaman login lebih jahat lagi. Di sana gejalanya terbaca sebagai "refresh blank", dan yang paling menyesatkan: kadang jalan. /login/?error=captcha sempat menampilkan notice dengan benar waktu saya tes pertama kali. Beberapa jam kemudian, di halaman yang sama, giliran ?error=1 yang datang tanpa parameternya dan notice-nya hilang lagi. Nondeterministik seperti itu adalah cara tercepat untuk membuat orang berhenti percaya sama hasil tesnya sendiri.

Diagnosa dua menit yang harusnya saya lakukan duluan

Setelah cukup lama menebak, saya berhenti menebak. Alih-alih menerka apa yang sampai ke template, saya cetak saja isinya. Satu baris, ditaruh di template yang bersangkutan:

<!-- DBG get=<?php echo esc_html( wp_json_encode( $_GET ) ); ?> -->

Saya taruh di dalam HTML comment supaya tidak merusak tampilan, lalu buka URL yang tadi dan lihat view-source. Hasilnya menutup seluruh perdebatan dalam sekali lihat:

<!-- DBG get={"match":"x","type":"company"} -->

Key match ada. Key type ada. Key error tidak ada.

Jadi masalahnya bukan di kondisi notice, bukan di template, bukan di cache. Parameternya memang tidak pernah sampai ke kode saya. Semua jam sebelumnya saya habiskan untuk men-debug cabang if yang datanya memang sudah kosong sejak awal.

Akar masalahnya: error bukan milik saya

error adalah salah satu nama query var publik yang sudah dipesan WordPress. Dia bukan nama bebas yang boleh kamu pakai untuk keperluan sendiri, dia bagian dari daftar var yang ikut diproses saat request di-parse.

Di stack situs ini, efeknya keras: parameter itu dibuang secara global sebelum template saya sempat melihatnya. Yang bikin diagnosisnya makin sulit, pembuangan itu tidak konsisten antar path, dan itulah kenapa halaman login sempat terlihat berfungsi lalu berhenti berfungsi tanpa ada yang saya ubah.

Saya sempat tergoda menelusuri siapa persis di rantai eksekusi yang menghapusnya, tapi berhenti karena tidak ada gunanya. Kalaupun ketemu, kesimpulannya tetap sama: selama saya memakai nama yang sudah dipesan, saya sedang berebut dengan sistem yang selalu menang.

Bukti pembandingnya rapi. Di alur yang sama, parameter status yang saya bikin sendiri tidak pernah sekali pun hilang. Bukan karena dia lebih beruntung, tapi karena tidak ada yang mengklaim nama itu.

Perbaikannya: kasih prefix punya sendiri

Solusinya tidak elegan, tidak pintar, dan selesai dalam sepuluh menit. Saya kasih prefix ke semua parameter buatan sendiri supaya tidak mungkin bentrok dengan nama bawaan:

// Sebelumnya, bentrok dengan query var bawaan
wp_safe_redirect( add_query_arg( 'error', 'exists', $return_url ) );
 
// Sesudahnya, nama milik sendiri
wp_safe_redirect( add_query_arg( 'req_error', 'exists', $return_url ) );

Di sisi template, tinggal ganti nama key yang dibaca:

$req_error = isset( $_GET['req_error'] ) ? sanitize_key( wp_unslash( $_GET['req_error'] ) ) : '';
 
if ( 'exists' === $req_error ) {
	echo '<p class="notice notice-error">Profil dengan data ini sudah terdaftar.</p>';
}

Saya pakai pola yang sama di seluruh situs: auth_error untuk alur login, req_error untuk alur pengajuan, dan prefix pendek yang seragam untuk parameter status lainnya. Begitu deploy, semua notice yang selama ini bisu langsung muncul, termasuk yang di halaman login. Tidak ada satu pun logika notice yang perlu saya ubah, karena logikanya memang sudah benar sejak awal.

Daftar nama yang jangan pernah kamu pakai

Ini bagian yang saya tempel di catatan supaya tidak kejeblos dua kali. Untuk parameter GET buatan sendiri di WordPress, hindari nama-nama ini:

Semuanya punya arti khusus buat WordPress. Sebagian menyebabkan parameter kamu dibuang seperti kasus di atas, sebagian lagi lebih parah karena justru mengubah query utama atau melempar kamu ke halaman lain. Aturan yang saya pakai sekarang sederhana: kalau parameter itu buatan saya, namanya harus punya prefix saya.

Yang saya bawa pulang