Aplikasi kecil bisa menampung seluruh controller dalam satu direktori datar. Namun begitu fitur bertambah, app/Http/Controllers berubah menjadi deretan puluhan berkas dengan nama seperti AdminDashboardController dan AdminPenggunaExportController yang saling bertabrakan secara konsepsi. Solusi yang disediakan Laravel sekaligus standar PHP adalah sub-namespace: mengelompokkan controller ke dalam sub-direktori yang mencerminkan domain aplikasi. Chapter ini membangun area admin sebagai contoh — direktori, namespace, route, sampai feature test-nya.

Membuat Controller di Sub-Direktori #

Generator menerima path relatif dengan pemisah garis miring. Perintah berikut membuat controller di dalam direktori Admin:

bash
php artisan make:controller Admin/DashboardController

Output yang Dihasilkan:

text
  INFO  Controller [app/Http/Controllers/Admin/DashboardController.php] created successfully.  

Isi berkas hasil generator memperlihatkan satu perubahan kunci dibanding controller yang selama ini kita buat:

php
<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class DashboardController extends Controller
{
    //
}

Baris namespace App\Http\Controllers\Admin; menjadikan nama lengkap kelas App\Http\Controllers\Admin\DashboardController. Generator juga otomatis menambahkan use App\Http\Controllers\Controller; agar extends Controller tetap menunjuk base controller yang benar, bukan kelas Controller di dalam namespace Admin yang tidak ada.

Pemetaan direktori ke namespace ini diatur PSR-4 di composer.json — aturan autoload yang sudah kita bahas pada Section Fondasi PHP untuk Laravel:

Lokasi berkasNamespaceNama lengkap kelas
Controllers/DashboardController.phpApp\Http\ControllersApp\Http\Controllers\DashboardController
Controllers/Admin/DashboardController.phpApp\Http\Controllers\AdminApp\Http\Controllers\Admin\DashboardController
Controllers/Admin/Laporan/PenjualanController.phpApp\Http\Controllers\Admin\LaporanApp\Http\Controllers\Admin\Laporan\PenjualanController

Routing ke Controller Bersarang #

Pendaftaran route memakai nama kelas lengkap lewat import. Kita lengkapi area admin dengan controller pengguna beserta satu invokable, lalu daftarkan semuanya dalam satu prefix group — pola yang sudah kita kenal dari chapter Route Groups:

php
<?php

use App\Http\Controllers\Admin\DashboardController;
use App\Http\Controllers\Admin\PenggunaController;
use App\Http\Controllers\Admin\PenggunaRingkasanController;
use Illuminate\Support\Facades\Route;

Route::prefix('admin')->name('admin.')->group(function () {
    Route::get('/dasbor', [DashboardController::class, 'index'])->name('dasbor');
    Route::get('/pengguna', [PenggunaController::class, 'index'])->name('pengguna');
    Route::get('/pengguna/ringkasan', PenggunaRingkasanController::class)->name('pengguna.ringkasan');
});

Tiga baris di dalam group menggabungkan seluruh materi Section ini: prefix admin. pada nama route, handler array untuk controller biasa, dan nama kelas polos untuk controller invokable. Kita periksa hasil pendaftarannya:

bash
php artisan route:list --except-vendor --path=admin

Output yang Dihasilkan:

text
  GET|HEAD       admin/dasbor ................................................... admin.dasbor › Admin\DashboardController@index
  GET|HEAD       admin/pengguna ................................................ admin.pengguna › Admin\PenggunaController@index
  GET|HEAD       admin/pengguna/ringkasan ......................... admin.pengguna.ringkasan › Admin\PenggunaRingkasanController

                                                                                                              Showing [3] routes

Kolom paling kanan kini menampilkan namespace Admin\ sebelum nama kelasnya — bukti visual bahwa kita memanggil controller yang berbeda dari controller area publik, meskipun namanya sama sekalipun. Justru di sinilah kekuatan namespace: App\Http\Controllers\Admin\DashboardController dan App\Http\Controllers\DashboardController adalah dua kelas yang sah dan boleh hidup berdampingan, sehingga tiap area bebas memakai nama paling natural tanpa perlu awalan seperti AdminDashboardController yang bertele-tele.

Menjaga Struktur Saat Membesar #

Sub-direktori paling natural mengikuti cara tim kita bicara tentang aplikasi. Area admin mengelola pengguna, area publik menampilkan konten, dan endpoint API melayani client — tiga dunia yang berhak punya folder sendiri. Batasi kedalaman nesting ke dua tingkat saja; direktori Controllers/Admin/Laporan/ masih terbaca wajar, tetapi rantai lima folder biasanya tanda bahwa domainnya perlu dipecah menjadi modul tersendiri:

DomainDirektoriContoh isi
Admin panelControllers/Admin/DashboardController, PenggunaController
Halaman publikControllers/ (root)PostController, KontakController
APIControllers/Api/PostApiController (generator --api)
Tugas sekali jalanControllers/Admin/ dst.PenggunaRingkasanController (invokable)

Feature test memastikan seluruh struktur tersebut benar-benar terjangkau lewat prefix URI:

php
<?php

namespace Tests\Feature;

use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class AdminAreaTest extends TestCase
{
    use RefreshDatabase;

    public function test_controller_admin_terjangkau_lewat_prefix(): void
    {
        Post::create(['title' => 'Belajar Controller', 'content' => 'Isi pertama.']);
        User::create([
            'name' => 'Budi',
            'email' => '[email protected]',
            'password' => 'rahasia-dummy',
        ]);

        $this->get('/admin/dasbor')->assertOk()->assertSee('total post: 1');
        $this->get('/admin/pengguna')->assertOk()->assertSee('total: 1');
        $this->get('/admin/pengguna/ringkasan')->assertOk()->assertSee('Ringkasan pengguna: 1');
    }
}

Output yang Dihasilkan:

text
  PASS  Tests\Feature\AdminAreaTest
  ✓  controller admin terjangkau lewat prefix                              0.19s  

  Tests:    1 passed (6 assertions)
  Duration: 0.27s

Enam assertion menegaskan bahwa namespace bersarang tidak mengubah cara kerja controller sama sekali — route, dependency injection, dan response semuanya berperilaku identik; yang berubah hanya alamat kelasnya. Struktur controller pun idealnya tercermin pada routes/web.php: satu blok prefix group per area, sehingga urutan route file mengikuti urutan folder controller dan keduanya mudah dibaca berdampingan. Konvensi selengkapnya, termasuk sub-direktori default Laravel lainnya, terdokumentasi di dokumentasi controllers .

Struktur direktori menjawab di mana controller ditempatkan. Satu pertanyaan terakhir yang lebih mendasar menunggu: apa saja yang boleh ditulis di dalamnya? Chapter Separation of Concerns di Controller — penutup Section ini — merangkai validasi dan logika bisnis ke lapisan yang tepat, menjadikan controller koordinator sejati.