Cara Menjalankan CodeIgniter 4 di Subfolder (localhost/nama-folder) dan php spark serve Sekaligus

Salah satu hal yang cukup sering bikin bingung saat mengembangkan aplikasi berbasis CodeIgniter 4 di lingkungan lokal (XAMPP/Laragon) adalah: aplikasi disimpan di dalam subfolder seperti htdocs/nama-folder atau www/nama-folder, tapi di sisi lain kita juga ingin tetap bisa menjalankannya lewat php spark serve tanpa perlu ubah-ubah konfigurasi setiap kali pindah cara menjalankan.

Catatan ini saya tulis berdasarkan proses troubleshooting yang saya lakukan sendiri di sebuah project CodeIgniter 4 (Laragon, folder project bernama policell), supaya tidak lupa dan bisa jadi referensi kalau ketemu masalah serupa di kemudian hari.

1. Latar Belakang

Secara default, CodeIgniter 4 melayani seluruh request dari folder public/. Kalau project disimpan di htdocs/nama-folder (XAMPP) atau www/nama-folder (Laragon), URL bawaannya jadi http://localhost/nama-folder/public/... — ada embel-embel /public yang mengganggu.

Yang saya inginkan sederhana saja:

  • Bisa diakses langsung lewat http://localhost/nama-folder/ (tanpa /public) saat dijalankan lewat Apache/Laragon.
  • Bisa juga tetap dijalankan lewat php spark serve (http://localhost:8080/) untuk development sehari-hari.
  • Kedua cara itu jalan tanpa perlu gonta-ganti file .env setiap kali pindah mode.
Kelihatannya sepele, tapi ternyata prosesnya melewati beberapa lapis masalah yang saling terkait — mulai dari validasi baseURL yang berubah di CodeIgniter 4.4+, sampai perilaku redirect() yang ternyata tidak selalu konsisten dengan base_url().

2. Gejala Masalah

Ada dua masalah berbeda yang muncul berurutan. Saya pisahkan supaya jelas urutan penyebab dan solusinya.

2.1 Error ConfigException baseURL

Percobaan pertama, saya kosongkan saja app.baseURL di .env dengan asumsi CodeIgniter akan otomatis menebak base URL dari request yang masuk (ini perilaku versi CI4 yang lebih lama). Hasilnya malah error:

Fatal error: Uncaught CodeIgniter\Exceptions\ConfigException: Config\App::$baseURL "/" is not a valid URL.

Ternyata mulai CodeIgniter 4.4+, class SiteURI memvalidasi baseURL secara ketat dan tidak lagi otomatis menebak dari string kosong seperti versi-versi sebelumnya. Jadi butuh cara lain untuk auto-detect.

2.2 Redirect Kehilangan Subfolder

Setelah baseURL berhasil di-auto-detect (lihat solusinya di bagian berikutnya), semua link biasa di halaman — yang dibangun pakai base_url() — sudah benar, lengkap dengan /nama-folder/-nya. Tapi begitu proses login, browser malah diarahkan ke:

http://localhost/admin/dashboard (404 Not Found)

padahal seharusnya ke http://localhost/nama-folder/admin/dashboard. Prefix folder-nya hilang. Anehnya, ini hanya terjadi pada hasil redirect()->to() di controller (misalnya setelah proses login atau logout) — link-link biasa di halaman tetap normal.

Sempat saya duga penyebabnya karena penulisan path dengan garis miring di depan, misalnya redirect()->to('/admin/dashboard'). Setelah garis miringnya dihapus jadi redirect()->to('admin/dashboard') pun ternyata masalahnya masih sama persis. Berarti akar masalahnya bukan di situ.

3. Solusi Lengkap

Berikut susunan solusi akhir yang benar-benar berhasil, urut dari awal.

3.1 Root .htaccess

Supaya folder public/ tidak perlu muncul di URL, tambahkan file .htaccess di root project (sejajar dengan folder app/, public/, system/, writable/ — bukan di dalam folder public/):

<IfModule mod_rewrite.c>
    RewriteEngine On

    RewriteCond %{THE_REQUEST} ^GET.*index\.php [NC]
    RewriteRule ^(.*)index\.php/?(.*)$ /$1$2 [R=301,NE,L]

    RewriteRule ^$ public/ [L]

    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ public/$1 [L]
</IfModule>

File ini yang bertugas mengarahkan semua request masuk (http://localhost/nama-folder/apa-saja) secara transparan ke folder public/ di baliknya.

Pastikan module mod_rewrite Apache aktif dan AllowOverride All untuk folder htdocs/www (default-nya memang sudah begini di instalasi XAMPP maupun Laragon).

3.2 Konfigurasi .env

Di file .env, kosongkan app.baseURL:

app.baseURL = ''

String kosong ini nanti yang jadi penanda bagi kode di Config/App.php (lihat poin berikutnya) untuk mengisi nilainya secara otomatis saat aplikasi berjalan.

3.3 Auto-detect baseURL di Config/App.php

Ini bagian intinya. Karena CodeIgniter 4.4+ tidak lagi menebak sendiri baseURL dari string kosong, saya tambahkan logic auto-detect manual lewat __construct() di app/Config/App.php:

public function __construct()
{
    parent::__construct();

    if ($this->baseURL === '') {
        $isHttps = (! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
            || ($_SERVER['SERVER_PORT'] ?? null) === '443';
        $scheme = $isHttps ? 'https' : 'http';
        $host   = $_SERVER['HTTP_HOST'] ?? 'localhost';

        $basePath = '';

        // php spark serve: docroot sudah langsung public/, tidak butuh base path.
        if (PHP_SAPI !== 'cli-server') {
            $scriptFilename = str_replace('\\', '/', $_SERVER['SCRIPT_FILENAME'] ?? '');
            $documentRoot   = str_replace('\\', '/', $_SERVER['DOCUMENT_ROOT'] ?? '');

            if ($documentRoot !== '' && str_starts_with($scriptFilename, $documentRoot)) {
                $relative = substr($scriptFilename, strlen($documentRoot));
                $pos      = strpos($relative, '/public/');
                $basePath = $pos !== false ? substr($relative, 0, $pos) : '';
            }
        }

        $this->baseURL = $scheme . '://' . $host . $basePath . '/';
    }
}

Poin pentingnya ada di dua hal:

  • PHP_SAPI === 'cli-server' — ini nilai yang pasti benar hanya kalau PHP sedang dijalankan lewat built-in server (php spark serve / php -S). Kalau kondisi ini terpenuhi, base path dikosongkan karena docroot bawaan spark serve memang sudah langsung ke folder public/.
  • Selisih SCRIPT_FILENAME dan DOCUMENT_ROOT — dua-duanya adalah path fisik di disk (bukan path "logis" hasil rewrite Apache), sehingga hasilnya konsisten untuk request apa pun. Contoh: SCRIPT_FILENAME = C:/laragon/www/policell/public/index.php dikurangi DOCUMENT_ROOT = C:/laragon/www, hasilnya /policell/public/index.php, lalu bagian sebelum /public/ itulah nama foldernya: /policell.
Keuntungan pendekatan ini: tidak perlu hardcode nama folder di mana pun. Kalau nanti folder project di-rename atau dipindah, tidak perlu edit App.php lagi sama sekali.

3.4 Bungkus redirect() dengan base_url()

Nah, ini penyebab masalah redirect yang sempat bikin bingung di bagian 2.2. Setelah ditelusuri, ternyata redirect()->to() di CodeIgniter 4 memakai jalur konversi URL yang berbeda dari base_url()/site_url() — meskipun sama-sama merujuk ke konfigurasi baseURL yang sama, hasilnya bisa tidak konsisten untuk kasus tertentu (salah satunya request POST seperti submit form login).

Solusinya: jangan andalkan redirect()->to() untuk mengonversi path relatif sendiri. Bungkus manual pakai base_url() yang sudah terbukti selalu benar:

// Sebelum (berpotensi kehilangan subfolder)
return redirect()->to('admin/dashboard');

// Sesudah (konsisten, pakai mekanisme yang sama dengan link biasa)
return redirect()->to(base_url('admin/dashboard'));

Terapkan pola ini di semua pemanggilan redirect()->to() di seluruh controller, bukan cuma yang berhubungan dengan login/logout saja — supaya konsisten dan tidak muncul masalah serupa di endpoint lain di kemudian hari.


4. Kenapa Pendekatan Ini Reliable

Sebelum sampai ke solusi final di atas, saya sempat mencoba pendekatan lain yang ternyata kurang pas:

  • Auto-detect dari SCRIPT_NAME — ini path "logis" hasil rewrite Apache, dan ternyata perilakunya bisa berbeda tergantung jenis request (terutama antara GET dan POST), sehingga hasil deteksi base path bisa tidak konsisten.
  • Hardcode nama folder manual (mis. $namaFolderProject = 'policell';) — ini sempat jadi solusi sementara yang bekerja, tapi punya kekurangan: harus diedit ulang setiap kali folder project di-rename atau dipindah.

Kombinasi SCRIPT_FILENAME + DOCUMENT_ROOT pada akhirnya jadi solusi paling stabil karena keduanya murni path fisik di sistem file (bukan hasil interpretasi ulang dari rewrite rule), sehingga nilainya konsisten untuk metode request apa pun. Ditambah dengan membungkus semua redirect() pakai base_url(), seluruh bagian aplikasi — baik link biasa maupun redirect — akhirnya memakai satu mekanisme resolusi URL yang sama persis.


5. Ringkasan / Cheat Sheet

Checklist supaya CodeIgniter 4 bisa diakses lewat localhost/nama-folder DAN php spark serve:
  1. Tambahkan root .htaccess yang redirect ke folder public/.
  2. Set app.baseURL = '' di .env.
  3. Tambahkan auto-detect baseURL di Config/App.php lewat __construct(), berbasis PHP_SAPI + selisih SCRIPT_FILENAME/DOCUMENT_ROOT.
  4. Pastikan mod_rewrite aktif & AllowOverride All.
  5. Bungkus semua redirect()->to('path') jadi redirect()->to(base_url('path')).

6. Penutup

Masalah base URL semacam ini sebenarnya cukup umum ditemui pengembang CodeIgniter (atau framework PHP lain) yang terbiasa kerja di lingkungan local server berbasis subfolder seperti XAMPP/Laragon. Kuncinya adalah memahami bahwa tidak semua fungsi URL helper di framework menggunakan jalur resolusi yang sama — kadang perlu dipaksa konsisten secara manual seperti pada kasus redirect() di atas.

Semoga catatan ini berguna buat saya sendiri di kemudian hari (atau siapa pun yang mampir dan mengalami masalah serupa) — dan tidak perlu mengulang proses troubleshooting dari nol lagi.

Posting Komentar