Service Container — Laravel

Service Container adalah fitur inti Laravel yang mengelola dependency injection secara otomatis. Tanpa DI (hardcoded dependency): class OrderController {…

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: MemberController meminta NotificationService di constructor-nya, tapi AppServiceProvider belum mendaftarkan apa-apa. Apa yang terjadi saat POST /members dibuka: 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);
    }
}

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";
    }
}

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

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.

Yang akan kamu pelajari