Service container adalah bagian Laravel yang tahu cara membuat objek-objek di app-mu, termasuk objek yang mereka butuhkan, lalu menyerahkannya ke yang meminta. Cara menyerahkan lewat parameter itu disebut dependency injection. Bayangin petugas perpustakaan yang harus mengabari anggota baru. Dia cukup bilang "tolong kirim kabar ke Citra". Kepala perpustakaan yang menentukan lewat apa: email minggu ini, WhatsApp bulan depan. Petugasnya gak perlu diajari ulang tiap kali caranya berubah. Di Laravel, petugasnya controller, "kirim kabar" itu sebuah interface, keputusan kepala perpustakaan itu binding di AppServiceProvider, dan yang menyerahkan pengirim yang tepat adalah container.
🧪 Tebakan cepat:
MemberControllermemintaNotificationServicedi constructor-nya, tapiAppServiceProviderbelum mendaftarkan apa-apa. Apa yang terjadi saatPOST /membersdibuka: kabarnya gak terkirim, Laravel memilih sendiri, atau error? Tebak dulu, jawabannya ada di bagian "Mendaftarkan binding".
Minta, jangan bikin sendiri
Dependency adalah objek yang dibutuhkan objek lain untuk bekerja. MemberController butuh pengirim kabar. Cara pertama, controller membuatnya sendiri:
public function store(Request $request)
{
$notifier = new EmailNotification(); // terkunci ke email
return response()->json(['sent' => $notifier->send('Selamat datang!')], 201);
}
Mau ganti ke WhatsApp, atau memakai pengirim palsu di test, kamu harus mengubah controller ini. Dengan dependency injection, controller cukup meminta:
class MemberController extends Controller
{
public function __construct(private NotificationService $notifier)
{
}
public function store(Request $request)
{
return response()->json(['sent' => $this->notifier->send('Selamat datang!')], 201);
}
}
private NotificationService $notifierdi constructor artinya "aku butuh sesuatu yang bisa mengirim kabar". Controller gak peduli siapa yang mengisinya.$this->notifier->send(...)memakai objek yang diserahkan, apa pun class aslinya.
Kontrak dan implementasinya
interface NotificationService
{
public function send(string $message): string;
}
class EmailNotification implements NotificationService
{
public function send(string $message): string
{
return "Email: $message";
}
}
class WhatsAppNotification implements NotificationService
{
public function send(string $message): string
{
return "WhatsApp: $message";
}
}
interfaceadalah kontrak: daftar method yang wajib ada, tanpa isi. Interface gak bisa dibuat dengannew.implements NotificationServiceberarti class itu berjanji punya methodsendpersis seperti di kontrak. Bunyi hasilnya ditentukan class-nya:Email: Selamat datang!atauWhatsApp: Selamat datang!.
Container membuatkan objeknya
Waktu POST /members masuk, container yang membuat controller-nya:
| Langkah | Yang dilakukan container |
|---|---|
| 1 | Route butuh MemberController, container mulai membuatnya |
| 2 | Constructor-nya minta NotificationService $notifier |
| 3 | NotificationService itu interface, jadi container mencari binding-nya: EmailNotification |
| 4 | Container membuat EmailNotification, constructor-nya kosong |
| 5 | Objek itu dimasukkan ke constructor MemberController, lalu store() dipanggil |
Hasilnya 201 dengan {"sent":"Email: Selamat datang!"}. Class biasa (bukan interface) gak perlu didaftarkan: container membacanya sendiri dan membuat isi constructor-nya, selama isinya juga class.
Mendaftarkan binding
Binding didaftarkan di method register() milik app/Providers/AppServiceProvider.php:
public function register(): void
{
$this->app->bind(NotificationService::class, EmailNotification::class);
}
Tanpa baris itu, langkah 3 buntu: container gak tahu class mana yang memenuhi kontraknya, dan request-nya berakhir 500:
Illuminate\Contracts\Container\BindingResolutionException
Target [App\Contracts\NotificationService] is not instantiable while building [App\Http\Controllers\MemberController].
Jadi jawabannya error, bukan diam dan bukan memilih sendiri. Itu jawaban tebakannya.
| Didaftarkan dengan | app(NotificationService::class) dua kali |
|---|---|
$this->app->bind(...) |
dua objek berbeda, $a === $b bernilai false |
$this->app->singleton(...) |
objek yang sama, $a === $b bernilai true |
app(...) atau app()->make(...) meminta objek langsung dari container, sama seperti yang terjadi diam-diam di constructor.
Mengganti implementasi
Kepala perpustakaan pindah ke WhatsApp? Ubah satu baris di register():
$this->app->bind(NotificationService::class, WhatsAppNotification::class);
MemberController gak disentuh, dan jawabannya jadi {"sent":"WhatsApp: Selamat datang!"}. Di test, kamu bisa menyerahkan pengirim palsu dengan $this->app->instance(NotificationService::class, new FakeNotification()), supaya gak ada kabar sungguhan yang terkirim (dibahas di pelajaran Testing).
Di latihan
Latihannya memakai container sungguhan: daftarkan EmailNotification untuk NotificationService dengan app()->bind(NotificationService::class, EmailNotification::class);, lalu minta app(NotificationService::class) dan cetak hasil send("Selamat datang!"). Di proyek Laravel, bind ditulis di register() service provider sebagai $this->app->bind(...). Keluarannya harus memuat Email: Selamat datang!, dan teks "Email harus ditulis di dalam class EmailNotification, bukan ditambahkan oleh pemanggilnya.
⚠️ Jebakan yang sering ketemu
- Interface tanpa binding.
Target [App\Contracts\NotificationService] is not instantiable. Daftarkan diregister()milikAppServiceProvider. - Binding ke class yang gak memenuhi kontrak. PHP menolak di constructor:
Argument #1 ($notifier) must be of type App\Contracts\NotificationService, App\Services\SmsGateway given. Class tujuannya harusimplements NotificationService. - Constructor yang minta teks.
__construct(string $apiKey)gak bisa ditebak container:Unresolvable dependency resolving [Parameter #0 [ <required> string $apiKey ]]. Daftarkan dengan closure:$this->app->bind(NotificationService::class, fn () => new WhatsAppNotification(config('services.whatsapp.key')));. newdi dalam controller. Binding apa pun gak berpengaruh, karena controller gak pernah meminta ke container.
TL;DR: Service container membuat objek dan mengisi constructor-nya, dan dependency injection berarti class meminta dependency lewat parameter (__construct(private NotificationService $notifier)), bukan membuatnya dengan new. Class biasa dibuat otomatis; interface butuh binding di register() milik AppServiceProvider: $this->app->bind(NotificationService::class, EmailNotification::class). Tanpa binding: BindingResolutionException, Target [...] is not instantiable. bind membuat objek baru tiap kali diminta, singleton memakai objek yang sama. Ganti implementasi cukup dengan mengubah binding, controller-nya gak berubah.