D
P
0
← Semua artikel Read in English

WordPress & PHP di Produksi

Dua add_rewrite_rule dengan Regex Sama Tidak Saling Mengisi, yang Kedua Menimpa yang Pertama

· · 3 menit baca
Dua add_rewrite_rule dengan Regex Sama Tidak Saling Mengisi, yang Kedua Menimpa yang Pertama

Di sebuah situs WordPress yang saya rawat, saya butuh sebuah URL prefix tunggal, misalnya /style/..., yang bisa melayani dua tipe konten: sebuah custom post type lebih dulu, dan kalau tidak ada, fallback ke post biasa. Idenya sederhana di kepala: daftarkan dua rewrite rule dengan regex yang sama, satu untuk CPT, satu untuk post, biarkan WordPress mencoba yang pertama lalu jatuh ke yang kedua.

Hasilnya: semua slug CPT 404. Hanya rule yang terakhir didaftarkan yang bertahan. Tidak ada chaining, tidak ada fallback.

Akar masalah: rewrite rule di-key berdasarkan regex

Inilah bagian yang menjebak. WordPress menyimpan rewrite rules sebagai sebuah array yang di-key oleh string regex-nya. Jadi kalau Anda mendaftarkan dua rule dengan regex identik tapi query string berbeda, yang kedua menimpa yang pertama, persis seperti $arr[$key] = $a; $arr[$key] = $b;. Hanya satu rule per regex unik yang bisa hidup.

Kode yang saya kira benar:

// SALAH — rule kedua menimpa rule pertama, tidak ada fallthrough
add_rewrite_rule( '^style/([^/]+)/?$', 'index.php?post_type=article&name=$matches[1]', 'top' );
add_rewrite_rule( '^style/([^/]+)/?$', 'index.php?name=$matches[1]', 'top' ); // ini yang menang

Karena kedua regex identik (^style/([^/]+)/?$), entri kedua menumpuk yang pertama. Yang tersisa hanyalah rule post. Slug yang sebenarnya milik CPT article tidak punya rule yang cocok lagi → 404.

WordPress memang tidak punya konsep "coba rule ini, kalau tidak match coba berikutnya" untuk regex yang sama. Setiap regex hanya boleh muncul sekali.

Solusi: satu rule + private query var + filter request

Karena hanya satu rule per regex yang bisa hidup, jangan lawan itu. Pakai satu rule saja, tandai dengan private query var, lalu mekarkan tanda itu di sebuah filter request menjadi array post_type.

// 1. SATU rule, dengan penanda query var sendiri
add_action( 'init', function () {
    add_rewrite_rule(
        '^style/([^/]+)/?$',
        'index.php?name=$matches[1]&my_multitype=1',
        'top'
    );
} );
 
// 2. Daftarkan query var penanda agar WP mengenalinya
add_filter( 'query_vars', function ( $vars ) {
    $vars[] = 'my_multitype';
    return $vars;
} );
 
// 3. Mekarkan penanda menjadi array post_type di filter request
add_filter( 'request', function ( $qv ) {
    if ( ! empty( $qv['my_multitype'] ) ) {
        $qv['post_type'] = array( 'post', 'article' );
        unset( $qv['my_multitype'] );
    }
    return $qv;
} );

Kuncinya ada di langkah 3: dengan $qv['post_type'] = array( 'post', 'article' ), WordPress mencari slug name di kedua tipe konten dalam satu query. Inilah "fallback" yang sebenarnya kita inginkan, tanpa perlu dua rewrite rule. Penanda my_multitype di-unset supaya tidak bocor ke query final.

Auto-flush rewrite rule setelah deploy

Rewrite rule baru tidak aktif sampai di-flush. Daripada manual menyimpan permalink settings, saya pasang auto-flush yang dipatok ke versi, dijalankan di init setelah rule didaftarkan:

add_action( 'init', function () {
    $version = '1.0.1'; // naikkan setiap kali rule berubah
    if ( get_option( 'my_rewrite_version' ) !== $version ) {
        flush_rewrite_rules();
        update_option( 'my_rewrite_version', $version );
    }
}, 99 ); // prioritas akhir, setelah add_rewrite_rule terdaftar

flush_rewrite_rules() itu mahal, jadi jangan dipanggil setiap request. Pola version-pin ini hanya flush sekali per deploy, saat versinya naik, lalu menyimpannya di wp_options.

Catatan penutup

Pelajaran intinya: rewrite rule itu sebuah map yang di-key oleh regex, bukan daftar berurut yang dicoba satu per satu. Begitu Anda memikirkannya sebagai map, solusinya jelas: satu key, satu rule, dan biarkan filter request yang mengurus percabangan.