Salah satu hal yang lumrah dialami pengguna Laravel di shared hosting adalah tidak tersedianya akses terminal/SSH. Padahal ada banyak momen dimana perintah php artisan dibutuhkan, misalnya untuk membersihkan cache setelah update kode. Tulisan ini merangkum langkah membuat endpoint khusus di aplikasi Laravel + Filament supaya perintah optimize:clear bisa dijalankan cukup lewat browser, lengkap dengan kendala yang muncul di tengah jalan dan cara mengatasinya.
Daftar isi
Masalah: Shared Hosting Tanpa Terminal
Banyak paket shared hosting hanya menyediakan akses lewat cPanel/file manager, tanpa SSH. Ini jadi masalah ketika aplikasi Laravel yang sudah di-deploy ke production memakai cache konfigurasi, route, atau view — begitu ada perubahan kode, cache lama bisa membuat perubahan tidak muncul, atau bahkan membuat aplikasi error, sampai cache-nya dibersihkan.
php artisan optimize untuk men-cache file konfigurasi, event, route, dan view supaya aplikasi lebih cepat di production. Kebalikannya, php artisan optimize:clear menghapus semua file cache tersebut sekaligus data di cache driver default, supaya aplikasi "mulai dari nol" lagi.
Solusi: Endpoint Khusus untuk Clear Cache
Karena tidak ada terminal, solusinya adalah membuat sebuah controller dan route baru di aplikasi Laravel yang, ketika diakses lewat URL tertentu, akan menjalankan perintah artisan dari dalam kode PHP itu sendiri — menggunakan facade Artisan::call() yang memang disediakan Laravel untuk menjalankan perintah artisan secara terprogram.
Controller & Route
Controller barunya (mis. MaintenanceController) memetakan sejumlah "slug" URL yang aman ke perintah artisan aslinya, misalnya slug optimize-clear dipetakan ke perintah optimize:clear. Pemetaan ini sengaja dibatasi hanya untuk perintah yang sifatnya membersihkan/optimasi cache — bukan sembarang perintah artisan seperti migrasi database — supaya endpoint ini tidak berubah jadi celah keamanan kalau ada yang menemukan URL-nya.
Route-nya didaftarkan sebagai GET biasa, misalnya:
Berikut isi lengkap app/Http/Controllers/MaintenanceController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
class MaintenanceController extends Controller
{
protected array $commandMap = [
'optimize-clear' => 'optimize:clear',
'optimize' => 'optimize',
'cache-clear' => 'cache:clear',
'config-clear' => 'config:clear',
'route-clear' => 'route:clear',
'view-clear' => 'view:clear',
'event-clear' => 'event:clear',
'filament-optimize-clear' => 'filament:optimize-clear',
];
public function run(Request $request, string $command)
{
$expectedToken = (string) config('maintenance.token', '');
$givenToken = (string) $request->query('token', '');
// Tolak kalau token di config/maintenance.php belum diganti dari
// nilai default — nilai default itu ditulis terbuka di kode ini,
// jadi kalau lupa diganti, siapa pun yang baca kode ini otomatis
// tahu tokennya.
if ($expectedToken === '' || $expectedToken === 'GANTI-DENGAN-TOKEN-RAHASIA-ANDA') {
abort(403, 'Token maintenance belum diatur.');
}
// hash_equals() dipakai (bukan ===) supaya perbandingan token tahan
// terhadap timing attack.
if (! hash_equals($expectedToken, $givenToken)) {
abort(403, 'Token tidak valid.');
}
if ($command === 'clear-all') {
$log = '';
foreach (['optimize:clear', 'filament:optimize-clear'] as $artisanCommand) {
Artisan::call($artisanCommand);
$log .= "=== php artisan {$artisanCommand} ===\n" . Artisan::output() . "\n";
}
return response("OK, semua cache berhasil dibersihkan.\n\n{$log}")
->header('Content-Type', 'text/plain; charset=UTF-8');
}
if (! array_key_exists($command, $this->commandMap)) {
abort(404, 'Perintah tidak dikenali.');
}
$artisanCommand = $this->commandMap[$command];
Artisan::call($artisanCommand);
$output = Artisan::output();
return response(
"OK, perintah berhasil dijalankan: php artisan {$artisanCommand}\n\n" .
"Output:\n" . ($output !== '' ? $output : '(tidak ada output)')
)->header('Content-Type', 'text/plain; charset=UTF-8');
}
}
Tambahan di routes/web.php:
use App\Http\Controllers\MaintenanceController;
use Illuminate\Support\Facades\Route;
Route::get('/maintenance/{command}', [MaintenanceController::class, 'run'])
->name('maintenance.run');
Proteksi Pakai Token Rahasia
Karena endpoint ini pada dasarnya adalah "pintu belakang" untuk mengeksekusi perintah artisan lewat web, wajib diberi proteksi. Caranya dengan mencocokkan sebuah token rahasia yang dikirim lewat query string dengan token yang tersimpan di sisi server, memakai fungsi hash_equals() supaya perbandingannya aman dari serangan timing attack.
env()), karena kalau file cache konfigurasi (config:cache) sedang bermasalah, Laravel bisa berhenti membaca file .env sama sekali — sehingga nilai token yang bergantung pada env() justru ikut hilang di saat paling dibutuhkan.
Isi config/maintenance.php:
<?php
return [
'token' => 'GANTI-DENGAN-TOKEN-RAHASIA-ANDA',
];
Token acak bisa dibuat dari komputer lokal dengan cara:
php -r "echo bin2hex(random_bytes(20));"
Atau bisa gunakan generator password atau bebas diisi sendiri yang penting cukup aman.
Kenapa Cache Filament Tidak Ikut Bersih
Jika aplikasi yang di deploy menggunakan Filament sebagai panel admin, ternyata menjalankan optimize:clear saja tidak cukup, karena perintah tersebut murni bawaan Laravel dan tidak menyentuh cache khusus milik Filament.
Filament punya dua jenis cache tambahan di luar cache Laravel:
- Cache komponen Filament — indeks resource, page, widget, dan komponen Livewire lain, disimpan di folder
bootstrap/cache/filament. - Cache ikon (Blade Icons) — Filament memakai paket Blade Icons yang performanya jauh lebih baik saat ikon-ikonnya di-cache.
Filament menyediakan perintah php artisan filament:optimize untuk men-cache keduanya sekaligus (gabungan dari filament:cache-components dan icons:cache), dan php artisan filament:optimize-clear untuk membersihkan keduanya sekaligus. Jadi solusinya: endpoint tadi perlu ditambah opsi untuk memanggil filament:optimize-clear juga, idealnya digabung jadi satu slug (mis. clear-all) yang menjalankan optimize:clear dan filament:optimize-clear berurutan dalam satu kali akses.
Kendala: Command Icon Cache Tidak Terdaftar
Begitu filament:optimize-clear dicoba lewat endpoint tadi, muncul error:
Penyebabnya cukup unik: paket Blade Icons yang dipakai Filament untuk mengelola cache ikon hanya mendaftarkan perintah icons:cache dan icons:clear miliknya ketika aplikasi berjalan dari console/terminal (dicek lewat runningInConsole() di dalam service provider paket tersebut). Karena permintaan datang dari browser (HTTP request biasa), kedua perintah itu dianggap "tidak ada", sehingga proses filament:optimize-clear gagal di tengah jalan saat mencoba membersihkan cache ikon.
register() pada AppServiceProvider milik aplikasi, memakai method $this->commands([...]) dari Laravel. Dengan begitu, perintah icons:cache dan icons:clear tetap terdaftar dan bisa dipanggil lewat Artisan::call(), sekalipun permintaannya datang dari web, bukan terminal.
Tambahan di app/Providers/AppServiceProvider.php:
public function register(): void
{
$this->commands([
\BladeUI\Icons\Console\CacheCommand::class,
\BladeUI\Icons\Console\ClearCommand::class,
]);
}
Setelah pendaftaran manual ini ditambahkan, endpoint clear-all baru bisa berjalan mulus membersihkan cache Laravel maupun cache Filament (komponen + ikon) sekaligus, tanpa perlu SSH sama sekali.
Contoh Penggunaan
Semua slug diakses lewat pola URL yang sama: /maintenance/{slug}?token=TOKEN_RAHASIA. Tinggal ganti bagian {slug} sesuai kebutuhan.
Bersihkan semuanya sekaligus (Laravel + Filament, ini yang paling sering dipakai):
https://domainkamu.com/maintenance/clear-all?token=TOKEN_RAHASIA
Hanya butuh bersihkan cache data aplikasi (mis. cache dari Cache::remember(), bukan config/route):
https://domainkamu.com/maintenance/cache-clear?token=TOKEN_RAHASIA
Hanya cache konfigurasi — dipakai kalau baru mengubah nilai di file config/*.php atau .env:
https://domainkamu.com/maintenance/config-clear?token=TOKEN_RAHASIA
Hanya cache route — dipakai kalau baru menambah/mengubah route:
https://domainkamu.com/maintenance/route-clear?token=TOKEN_RAHASIA
Hanya cache view (Blade) — dipakai kalau baru mengubah file .blade.php tapi perubahannya belum muncul:
https://domainkamu.com/maintenance/view-clear?token=TOKEN_RAHASIA
Semua cache bawaan Laravel sekaligus (setara optimize:clear, tanpa menyentuh cache Filament):
https://domainkamu.com/maintenance/optimize-clear?token=TOKEN_RAHASIA
Hanya cache Filament (komponen + ikon) — dipakai kalau baru menambah resource/page/widget Filament baru:
https://domainkamu.com/maintenance/filament-optimize-clear?token=TOKEN_RAHASIA
Kesimpulan
Ada tiga poin penting dari kasus ini bagi siapa pun yang mengelola aplikasi Laravel/Filament di shared hosting tanpa akses terminal:
- Perintah artisan apa pun bisa dijalankan secara terprogram dari dalam kode lewat
Artisan::call(), sehingga bisa "dibungkus" jadi endpoint web yang diproteksi token. optimize:clearhanya membersihkan cache bawaan Laravel — kalau memakai Filament, cache komponen dan ikonnya perlu dibersihkan terpisah lewatfilament:optimize-clear.- Sebagian command pihak ketiga (seperti command milik paket Blade Icons) sengaja hanya terdaftar saat berjalan dari console, sehingga kalau ingin dipanggil dari luar terminal (web/queue/scheduler), command tersebut perlu didaftarkan manual di service provider aplikasi.
Referensi
- Laravel. Deployment — Optimization (optimize & optimize:clear).
- Filament. Installation — Improving Filament Panel Performance.

Posting Komentar