Service container
Container adalah jantung Laravel. Memahaminya mengubah framework dari kumpulan fitur ajaib jadi sistem yang bisa kamu arahkan — dan membuat kodemu bisa diuji tanpa menyentuh dunia luar.
Intisari
- Ketik sebuah kelas di parameter, dan container membangunnya beserta seluruh dependensinya — rekursif.
bindmembuat instance baru tiap kali;singletonmembuatnya sekali per siklus.- Untuk interface, kamu harus memberi tahu container implementasi mananya (
bind(Interface::class, Konkret::class)). app()->instance()danswap()adalah cara tes mengganti layanan nyata dengan tiruan.- Peringatan Fase 8: singleton yang menyimpan data per-permintaan akan membocorkan data antar pengguna di worker mode.
Resolusi otomatis
class LaporanPenjualan
{
public function __construct(
private readonly KursMataUang $kurs,
private readonly PenyimpananBerkas $berkas,
) {}
}
// Kamu tidak pernah menulis ini:
$laporan = new LaporanPenjualan(new KursMataUang(new HttpClient()), new PenyimpananBerkas());
// Cukup ini — container menelusuri tipe dan membangun seluruh rantainya:
$laporan = app(LaporanPenjualan::class);
// Atau, lebih sering, kamu tidak menulis apa pun:
public function index(LaporanPenjualan $laporan) { /* sudah terisi */ }
Container membaca tipe di constructor, membangun tiap dependensi, lalu membangun dependensi dari dependensi itu. Selama semuanya berupa kelas konkret dengan tipe yang jelas, tidak ada konfigurasi yang perlu ditulis sama sekali.
Kapan kamu harus turun tangan
// app/Providers/AppServiceProvider.php
public function register(): void
{
// 1. Interface → implementasi. Container tidak bisa menebak ini.
$this->app->bind(GerbangPembayaran::class, MidtransGateway::class);
// 2. Konstruktor butuh nilai yang bukan kelas
$this->app->singleton(KlienPeta::class, fn ($app) => new KlienPeta(
kunci: config('services.peta.key'),
timeout: 5,
));
// 3. Implementasi berbeda per lingkungan
$this->app->bind(PengirimSms::class, fn ($app) =>
$app->environment('production')
? new SmsAsli(config('services.sms.token'))
: new SmsCatatSaja());
}
| Metode | Perilaku | Pakai untuk |
|---|---|---|
bind | Objek baru setiap kali diminta | Objek yang menyimpan keadaan |
singleton | Satu objek, dipakai ulang | Klien HTTP, koneksi, konfigurasi |
scoped | Satu objek per permintaan, direset di worker mode | Konteks tenant, keranjang belanja |
instance | Daftarkan objek yang sudah jadi | Terutama di dalam tes |
scoped ada khusus untuk worker mode. Di PHP-FPM, singleton dan
scoped berperilaku sama, karena prosesnya mati tiap permintaan. Di Octane, singleton
bertahan antar permintaan sementara scoped dibuat ulang. Kalau layanan menyimpan sesuatu milik
pengguna tertentu, ia harus scoped. Salah pilih di sini adalah cara paling mudah
membocorkan data satu pengguna ke pengguna lain — dibahas tuntas di Fase 8.
Kenapa ini penting untuk testing
public function test_pesanan_menagih_lewat_gerbang(): void
{
// Ganti implementasi nyata dengan tiruan — kode aplikasi tidak berubah sedikit pun
$gerbang = Mockery::mock(GerbangPembayaran::class);
$gerbang->shouldReceive('tagih')->once()->andReturn(true);
$this->app->instance(GerbangPembayaran::class, $gerbang);
$this->post('/pesanan', [...])->assertRedirect();
}
Inilah alasan sebenarnya bergantung pada interface, bukan pada kelas konkret: bukan demi kemurnian desain, melainkan supaya tes tidak perlu memanggil gerbang pembayaran sungguhan.
Fitur container yang jarang diketahui
// Contextual binding — dependensi sama, implementasi berbeda per pemakai
$this->app->when(EksporLaporan::class)
->needs(Filesystem::class)
->give(fn () => Storage::disk('s3'));
// Tagging — ambil sekelompok layanan sekaligus
$this->app->tag([PajakPPN::class, PajakDaerah::class], 'pajak');
$this->app->bind(HitungPajak::class, fn ($app) =>
new HitungPajak(iterator_to_array($app->tagged('pajak'))));
// Memanggil method dengan dependensi terinjeksi
app()->call([$laporan, 'buat'], ['bulan' => 8]);
Tagging adalah pola yang rapi untuk aturan yang bisa bertambah. Menambahkan jenis pajak baru cukup
dengan menambah satu kelas dan satu baris tag — kelas HitungPajak tidak perlu disentuh. Ini bentuk
praktis dari prinsip terbuka-tertutup, tanpa kerangka kerja tambahan apa pun.
Yang tidak bisa di-resolve otomatis
| Bentuk | Kenapa gagal | Perbaikan |
|---|---|---|
| Parameter tanpa tipe | Container tidak tahu harus mengisi apa | Tulis tipenya, atau daftarkan closure |
| Parameter bertipe interface | Tidak tahu implementasi mana | bind(Interface::class, ...) |
Parameter string / int | Bukan kelas | Closure, atau ->when()->needs()->give() |
| Ketergantungan melingkar | A butuh B, B butuh A | Ini pertanda desainnya perlu dipecah |
Latihan: buat interface PengirimNotifikasi dengan dua implementasi
(Email dan CatatSaja), ikat berdasarkan environment di AppServiceProvider,
lalu suntikkan ke sebuah controller. Setelah jalan, tulis satu tes yang mengganti implementasinya dengan
$this->app->instance(...) dan memastikan method-nya terpanggil.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.