Sejauh ini kita menulis signature seperti show(Post $post) atau __invoke(Request $request) tanpa pernah membuat objeknya sendiri — tidak ada new Post() maupun new Request() di kode kita. Objek-objek tersebut diisi oleh service container, pusat manajemen dependency Laravel yang memeriksa type hint setiap parameter lalu menyediakan instance yang tepat secara otomatis. Chapter ini membahas cara kerja suntikan tersebut pada method dan constructor controller, cara container mengikat interface ke implementasi konkret, serta apa yang terjadi bila container tidak tahu harus mengisi apa.
Method Injection #
Bentuk paling sering kita jumpai adalah method injection: setiap parameter method controller yang bertipe kelas akan diisi container. Kita buktikan dengan service kecil pembuat slug:
<?php
namespace App\Services;
use Illuminate\Support\Str;
class PembuatSlug
{
public function buat(string $judul): string
{
return Str::slug($judul);
}
}Kemampuan yang sama berlaku untuk dependency buatan sendiri, bukan hanya kelas bawaan Laravel. Controller berikut menerima service tersebut sebagai parameter method:
<?php
namespace App\Http\Controllers;
use App\Contracts\PenghitungDiskon;
use Illuminate\Http\Request;
class KeranjangController extends Controller
{
public function cek(Request $request, PenghitungDiskon $penghitung)
{
$total = (int) $request->input('total', 0);
$diskon = $penghitung->hitung($total);
return "Diskon Rp{$diskon}, total bayar Rp".($total - $diskon);
}
}Perhatikan bahwa parameter PenghitungDiskon pada method cek() adalah sebuah interface, bukan kelas konkret. Saat request GET /keranjang/cek masuk, container membaca type hint setiap parameter, mencari kelas atau binding yang cocok, lalu memanggil method dengan instance tersebut — urutan parameter bebas, dan Request $request boleh berada di posisi mana pun.
Interface ke Implementasi #
Interface tidak bisa diinstansiasi langsung, jadi kita perlu memberi tahu container implementasi mana yang dipakai. Pengikatan ditulis di AppServiceProvider:
<?php
namespace App\Providers;
use App\Contracts\PenghitungDiskon;
use App\Services\DiskonSepuluhPersen;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->bind(PenghitungDiskon::class, DiskonSepuluhPersen::class);
}
public function boot(): void
{
//
}
}Method bind() mengikati interface PenghitungDiskon ke kelas DiskonSepuluhPersen yang mengimplementasikannya. Perilaku container tersebut dapat kita lihat langsung lewat skrip runner:
<?php
// uji-di.php — jalankan dari root proyek Laravel
$dir = $argv[1];
require $dir.'/vendor/autoload.php';
$app = require $dir.'/bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
use App\Contracts\PenghitungDiskon;
use App\Services\PembuatSlug;
use Illuminate\Support\Facades\App;
$satu = App::make(PenghitungDiskon::class);
echo 'Class di balik interface: '.get_class($satu).PHP_EOL;
$slug1 = App::make(PembuatSlug::class);
$slug2 = App::make(PembuatSlug::class);
echo 'PembuatSlug baru setiap make: '.($slug1 !== $slug2 ? 'ya' : 'bukan').PHP_EOL;
echo 'Diskon untuk 250000: '.$satu->hitung(250000).PHP_EOL;Output yang Dihasilkan:
Class di balik interface: App\Services\DiskonSepuluhPersen
PembuatSlug baru setiap make: ya
Diskon untuk 250000: 25000Baris pertama membuktikan App::make() pada interface menghasilkan objek DiskonSepuluhPersen sesuai binding. Baris kedua menunjukkan perilaku bawaan bind(): setiap resolusi membuat instance baru. Bila kita ingin satu instance dibagi sepanjang hidup aplikasi, ganti bind() menjadi singleton() — pilihan yang relevan untuk service yang menyimpan status.
Constructor Injection #
Dependency yang dipakai banyak method lebih rapi ditempatkan di constructor:
<?php
namespace App\Http\Controllers;
use App\Services\PembuatSlug;
use Illuminate\Http\Request;
class HalamanController extends Controller
{
public function __construct(
protected PembuatSlug $pembuatSlug,
) {}
public function susun(Request $request)
{
$slug = $this->pembuatSlug->buat($request->input('judul', 'Tanpa Judul'));
return "Slug yang dihasilkan: {$slug}";
}
}Container membangun controller lewat constructor-nya terlebih dahulu, sehingga $this->pembuatSlug siap dipakai semua method. Pilihan antara kedua gaya mengikuti cakupan pemakaian: dependency yang hanya dibutuhkan satu method lebih jelas dinyatakan sebagai parameter method itu sendiri, sedangkan dependency yang dipakai banyak method — seperti service di atas — pantas menempati constructor agar tidak diketik berulang. Keduanya bisa hadir bersamaan pada satu kelas, dan container menangani keduanya tanpa konfigurasi tambahan. Kita uji kedua controller tersebut sekaligus:
PASS Tests\Feature\DependencyInjectionTest
✓ service disuntikkan lewat constructor 0.10s
✓ interface disuntikkan lewat method 0.01s
Tests: 2 passed (4 assertions)
Duration: 0.18sSaat Container Tidak Bisa Mengisi #
Container hanya mampu mengisi parameter yang jelas sumbernya: kelas konkret (diinstansiasi lewat reflection) atau interface yang sudah diikat. Bila binding dihapus dari provider, request ke /keranjang/cek berhenti dengan BindingResolutionException dan pesan pada log:
Target [App\Contracts\PenghitungDiskon] is not instantiable.Pesan tersebut adalah cara container mengatakan: type hint interface ditemukan, tetapi tidak ada informasi implementasi mana yang harus dibuat. Solusinya selalu salah satu dari dua: ikat interface di provider, atau ganti type hint ke kelas konkret.
Dengan dependency yang dapat datang dari mana saja, fokus controller berikutnya adalah ujung kedua alur: bagaimana menghasilkan response dari berbagai bentuk — view, teks, header kustom, hingga response tanpa isi. Detail container secara menyeluruh tersedia di dokumentasi container , dan pembahasan response-nya kita mulai di chapter Request dan Response di Controller berikutnya.