Controller

Controller dasar, resource controller, dan dependency injection pada metode aksinya.

Daftar isi

Controller adalah kelas yang mengelompokkan penanganan beberapa rute terkait dalam satu berkas, sehingga logika penanganan permintaan tidak lagi tersebar sebagai closure panjang pada routes/web.php. Topik ini membahas pembuatan controller dengan Artisan, pengaitan rute ke metode aksinya, pola resource controller untuk CRUD, dan dependency injection atau injeksi dependensi pada metode aksi, semuanya diterapkan pada kelas HalamanController di proyek contoh.

Controller dasar#

Controller baru dihasilkan perintah php artisan make:controller, dan keluarannya berupa kelas kosong yang siap diisi metode aksi. Perintah berikut membuat controller HalamanController untuk proyek contoh, tetapi tidak dijalankan ulang di sini karena kelasnya sudah ada.

bash
# tidak-dijalankan
php artisan make:controller HalamanController

Keluaran asli perintah tersebut saat pertama kali dijalankan pada proyek contoh adalah sebagai berikut.

text Hasil perintah make:controller

   INFO  Controller [app/Http/Controllers/HalamanController.php] created successfully.  

Kelas yang dihasilkan berada di app/Http/Controllers dengan namespace App\Http\Controllers dan mewarisi kelas dasar Controller. Isi berkas setelah tiga metode aksi ditambahkan adalah sebagai berikut.

php app/Http/Controllers/HalamanController.php
// tidak-dijalankan
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\View\View;

class HalamanController extends Controller
{
    /**
     * Menampilkan halaman beranda aplikasi.
     */
    public function index(): View
    {
        return view('beranda', ['judul' => 'Beranda aplikasi catatan']);
    }

    /**
     * Menampilkan halaman tentang aplikasi.
     */
    public function tentang(): View
    {
        return view('tentang', ['judul' => 'Tentang aplikasi catatan']);
    }

    /**
     * Menjawab permintaan dengan menyebut jalurnya.
     */
    public function sapa(Request $request): string
    {
        return 'Permintaan untuk ' . $request->path();
    }
}

Setiap metode publik dapat menjadi aksi rute. Dua metode pertama mengembalikan view beserta datanya dalam bentuk array pada argumen kedua view(), sedangkan metode sapa digunakan pada bagian injeksi dependensi di bawah. Metode aksi dikaitkan ke rute dengan sintaks [Kelas::class, 'metode'] seperti berikut.

php routes/web.php
// tidak-dijalankan
<?php

use App\Http\Controllers\HalamanController;
use Illuminate\Support\Facades\Route;

Route::get('/', [HalamanController::class, 'index']);
Route::get('/tentang', [HalamanController::class, 'tentang']);
Route::get('/sapa', [HalamanController::class, 'sapa']);

Dua view yang dirujuk oleh metode index() dan tentang() berisi templat singkat berikut.

blade beranda.blade.php
<h1>{{ $judul }}</h1>
<p>Tempat menyimpan catatan pribadi.</p>
blade tentang.blade.php
<h1>{{ $judul }}</h1>
<p>Contoh aplikasi untuk jalur tutorial Laravel.</p>

Tempatkan keempat berkas di atas pada lokasinya, yaitu controller di app/Http/Controllers, rute di routes/web.php, dan kedua view di resources/views. Daftar rute kemudian menampilkan nama metode aksi sebagai penangan setiap URI.

bash
# tempatkan berkas controller, rute, dan view pada proyek uji
cp HalamanController.php ~/aplikasi-catatan/app/Http/Controllers/HalamanController.php
cp web.php ~/aplikasi-catatan/routes/web.php
mkdir -p ~/aplikasi-catatan/resources/views
cp beranda.blade.php ~/aplikasi-catatan/resources/views/beranda.blade.php
cp tentang.blade.php ~/aplikasi-catatan/resources/views/tentang.blade.php
cd ~/aplikasi-catatan
# siapkan routes/api.php minimal agar keluaran route:list tetap sama
cat > routes/api.php <<'PHP'
<?php

use Illuminate\Support\Facades\Route;

Route::get('/status', function () {
    return response()->json(['status' => 'ok']);
});
PHP
# tampilkan rute beserta aksi controllernya
php artisan route:list --except-vendor
text Keluaran
  GET|HEAD       / ................................... HalamanController@index
  GET|HEAD       api/status ................................. routes/api.php:5
  GET|HEAD       sapa ................................. HalamanController@sapa
  GET|HEAD       tentang ........................... HalamanController@tentang

                                                            Showing [4] routes

Kolom aksi kini memuat HalamanController@index alih-alih lokasi berkas, yang menandakan rute sudah ditangani controller. Baris berawalan api/ berasal dari routes/api.php yang dibuat pada blok perintah di atas dan tidak terkait controller yang baru ditambahkan. Isi responsnya dapat diperiksa dengan permintaan sintetis ke halaman beranda.

bash
cd ~/aplikasi-catatan
# kirim permintaan GET ke halaman beranda
php artisan tinker --execute='echo app(\Illuminate\Contracts\Http\Kernel::class)->handle(\Illuminate\Http\Request::create("/"))->getContent();'
text Keluaran
<h1>Beranda aplikasi catatan</h1>
<p>Tempat menyimpan catatan pribadi.</p>

HTML di atas adalah hasil render beranda.blade.php dengan variabel $judul yang dikirim controller. Alurnya mengikuti siklus permintaan HTTP pada Laravel, yaitu router memanggil metode aksi controller, lalu nilai yang dikembalikan diubah menjadi respons.

Resource controller#

Operasi CRUD pada sebuah data mengikuti pola URI dan metode HTTP yang berulang untuk setiap sumber daya. Laravel menyediakan resource controller untuk pola ini, yaitu controller yang metode-metodenya sudah mengikuti pola tersebut. Perintah berikut menghasilkan controller tersebut, dan keluaran aslinya diperlihatkan sesudahnya.

bash
# tidak-dijalankan
php artisan make:controller CatatanController --resource
text Hasil perintah make:controller --resource

   INFO  Controller [app/Http/Controllers/CatatanController.php] created successfully.  

Kelas hasil perintah tersebut berisi tujuh metode kosong yang masing-masing melayani satu operasi pada sumber daya bernama catatan.

MetodeURIMetode HTTPNama rute
index/catatanGETcatatan.index
create/catatan/createGETcatatan.create
store/catatanPOSTcatatan.store
show/catatan/{catatan}GETcatatan.show
edit/catatan/{catatan}/editGETcatatan.edit
update/catatan/{catatan}PUT/PATCHcatatan.update
destroy/catatan/{catatan}DELETEcatatan.destroy

Seluruh rute pada tabel itu terdaftar sekaligus dengan satu baris Route::resource('catatan', CatatanController::class); pada routes/web.php. Pola ini menyeragamkan URI aplikasi, dan daftar rutenya selalu dapat diperiksa ulang melalui php artisan route:list.

Injeksi dependensi pada metode aksi#

Metode aksi dapat menuliskan type hint dependensi pada daftar parameternya, dan service container Laravel menyuntikkannya secara otomatis ketika rute dieksekusi. Mekanisme inilah yang digunakan metode sapa() pada controller di atas, yaitu menuliskan Illuminate\Http\Request sebagai type hint agar objek permintaan yang sedang berjalan tersedia di dalam metode tanpa perlu dibuat manual.

Nilai path() pada objek Request adalah URI permintaan tanpa nama domain. Respons metode tersebut menunjukkan bahwa injeksi tersebut benar-benar terjadi.

bash
cd ~/aplikasi-catatan
# kirim permintaan ke rute yang aksinya menyuntikkan Request
php artisan tinker --execute='echo app(\Illuminate\Contracts\Http\Kernel::class)->handle(\Illuminate\Http\Request::create("/sapa"))->getContent();'
text Keluaran
Permintaan untuk sapa

Teks sapa pada keluaran berasal dari path() pada objek Request yang disuntikkan ke metode. Injeksi yang sama berlaku untuk dependensi lain, termasuk parameter rute yang diletakkan setelah dependensi pada daftar parameter, serta constructor injection, yaitu penyuntikan dependensi saat controller dibuat.

Kesalahan umum#

Kesalahan pertama adalah rute menunjuk view yang belum dibuat. Pemanggilan view() dengan nama yang tidak ada melempar exception yang menyebut nama view tersebut, sebagaimana terekam pada percobaan berikut.

bash
cd ~/aplikasi-catatan
# coba render view yang tidak ada
php artisan tinker --execute='view("tidak-ada")->render();'
text Keluaran
   InvalidArgumentException  View [tidak-ada] not found.

Pesan View [tidak-ada] not found. berarti nama pada view() harus cocok dengan nama berkas di resources/views, tanpa akhiran .blade.php. Kesalahan kedua adalah lupa mengimpor kelas controller pada routes/web.php, sehingga router gagal membuat kelasnya dan melaporkan error Class "..." not found ketika rute diakses. Perbaikannya adalah menambahkan use App\Http\Controllers\HalamanController; di bagian atas berkas rute. Kesalahan ketiga adalah menempatkan logika domain yang panjang ke dalam controller. Controller yang bertugas hanya membaca masukan, memanggil lapisan lain, dan mengembalikan respons lebih mudah diuji daripada controller yang menampung seluruh logika aplikasi.

Kesalahan keempat adalah menempatkan pekerjaan berat pada constructor controller, misalnya membaca data dari database. Constructor dijalankan pada setiap permintaan yang menuju controller tersebut, sebelum aksi tujuan diketahui, sehingga pekerjaan yang sebenarnya hanya diperlukan satu aksi tetap dieksekusi oleh aksi lainnya. Data semacam itu diambil di dalam metode aksinya sendiri, sedangkan constructor hanya digunakan untuk menyimpan dependensi ringan seperti layanan yang diinjeksi melalui type hint.

Ringkasan#

  • Controller dibuat dengan php artisan make:controller, berada di app/Http/Controllers, dan aksinya dikaitkan ke rute dengan sintaks [Kelas::class, 'metode'].
  • Resource controller dari make:controller --resource menyediakan tujuh metode CRUD yang seluruh rutenya didaftarkan oleh Route::resource().
  • Metode aksi menerima dependensi yang dituliskan sebagai type hint pada parameternya melalui injeksi otomatis dari service container, misalnya objek Request.
  • Nama pada view() merujuk berkas di resources/views tanpa akhiran .blade.php, dan nama yang salah melempar InvalidArgumentException berisi nama view tersebut.

Referensi#

  • Controllers — Laravel — Dokumentasi resmi controller yang menjadi dasar pembahasan aksi dasar, resource controller, dan injeksi dependensi pada topik ini.
  • Routing — Laravel — Bagian parameter rute dan urutan penyuntikannya yang dirujuk pada penjelasan injeksi dependensi.