Laravel Service Container Di Facades Service Providers
Laravel punya beberapa konsep yang sering disebut berdekatan, tapi fungsinya beda. Kalau dicampur, project biasanya masih jalan dulu—sampai arsitekturnya membesar dan perilaku dependency mulai sulit dilacak.
Definisi singkat
Service Container
Service Container adalah pusat manajemen dependency di Laravel. Di sinilah Laravel menyimpan aturan tentang bagaimana sebuah class atau interface dibuat saat dibutuhkan.
Dependency Injection
Dependency Injection adalah pola cara memberikan dependency dari luar ke class, bukan membuatnya langsung di dalam class.
Facades
Facade adalah lapisan akses statik yang nyaman untuk memanggil service di container. Di balik tampilannya yang statik, Facade tetap mengarah ke instance yang dikelola container.
Service Provider
Service Provider adalah tempat Laravel mendaftarkan binding, singleton, event, dan bootstrap logic ke aplikasi.
Perbedaan inti
| Konsep | Fokus | Tugas utama | Contoh |
|---|---|---|---|
| Service Container | Resolusi object | Membangun dependency | app()->make(UserRepository::class) |
| Dependency Injection | Desain class | Menerima dependency dari luar | __construct(UserRepository $repo) |
| Facade | Cara akses | Memanggil service dengan sintaks ringkas | Cache::get('key') |
| Service Provider | Registrasi | Mendaftarkan binding dan bootstrap | AppServiceProvider::register() |
Intinya begini:
- Container = tempat Laravel tahu cara membuat object.
- DI = cara class menerima object itu.
- Facade = cara memanggil service secara praktis.
- Provider = tempat binding dan bootstrap disiapkan.
Alur binding → resolve → use
Mermaid di bawah ini menunjukkan alurnya secara sederhana.
Urutannya penting:
- Provider mendaftarkan binding.
- Container menyimpan aturan pembuatan object.
- Saat class butuh dependency, container melakukan resolve.
- Object hasil resolve dipakai lewat constructor injection, method injection, atau Facade.
Kapan Dependency Injection dipakai
Dependency Injection dipakai saat class bergantung pada service lain dan kita ingin hubungan itu eksplisit.
Cocok untuk DI
- Repository
- Service class
- Notifier
- Client ke API eksternal
- Formatter / parser
- Class yang perlu mudah diuji dengan mock atau fake
Contoh
use App\Repositories\UserRepository;
class UserService
{
public function __construct(
private UserRepository $users,
) {}
public function activeCount(): int
{
return $this->users->countActive();
}
}Di sini UserService tidak membuat UserRepository sendiri. Ia hanya menyatakan bahwa ia membutuhkan repository itu.
Kenapa ini penting
Kalau dependency dibuat langsung di dalam class, misalnya dengan new, class jadi:
- lebih sulit dites,
- lebih sulit diganti implementasinya,
- lebih sulit dibaca saat dependensinya makin banyak.
Kapan binding dibutuhkan
Binding dibutuhkan saat Laravel tidak bisa atau tidak seharusnya menebak cara membuat object.
Binding diperlukan ketika:
1. Mengikat interface ke implementasi
Ini kasus paling umum.
use App\Contracts\PaymentGateway;
use App\Services\MidtransPaymentGateway;
$this->app->bind(PaymentGateway::class, MidtransPaymentGateway::class);Kalau controller meminta PaymentGateway, container akan memberi MidtransPaymentGateway.
2. Object butuh konfigurasi runtime
Misalnya client HTTP yang butuh base URL, token, atau opsi tertentu.
$this->app->singleton(ReportClient::class, function () {
return new ReportClient(config('services.report.url'));
});3. Perlu singleton
Kalau satu instance ingin dipakai ulang selama request yang sama.
$this->app->singleton(MailerFactory::class, fn () => new MailerFactory());4. Perlu contextual binding
Kalau implementasi yang dipakai berbeda tergantung class pemakainya.
$this->app->when(AdminReportController::class)
->needs(ReportRenderer::class)
->give(AdminReportRenderer::class);Kapan binding tidak perlu
Binding sering tidak perlu kalau:
- class konkret sederhana dan bisa di-autowire langsung,
- constructor-nya hanya berisi dependency yang juga bisa di-resolve otomatis,
- tidak ada kebutuhan mengganti implementasi.
Laravel cukup pintar untuk banyak kasus. Jadi binding bukan default untuk semua hal. Binding dipakai saat ada kontrak, variasi implementasi, konfigurasi, atau lifecycle khusus.
Bagaimana Service Provider mendaftarkan layanan
Service Provider adalah titik masuk resmi untuk menaruh binding dan bootstrap logic.
Dua metode utama
register()
Dipakai untuk mendaftarkan binding ke container.
use App\Contracts\PaymentGateway;
use App\Services\MidtransPaymentGateway;
public function register(): void
{
$this->app->bind(PaymentGateway::class, MidtransPaymentGateway::class);
}Aturan praktisnya:
- taruh binding di sini,
- hindari logic yang butuh service yang belum selesai di-bootstrap kalau tidak perlu.
boot()
Dipakai setelah semua provider didaftarkan.
public function boot(): void
{
// event listener, macro, view composer, policy, atau setup lain
}Bagian ini cocok untuk hal yang butuh aplikasi sudah siap.
Provider dan file registrasi
Secara konsep, provider adalah tempat menyusun bootstrap aplikasi. Saat aplikasi start:
- Laravel memuat provider.
register()dijalankan untuk binding.boot()dijalankan untuk inisialisasi lanjutan.- Saat request berjalan, container siap resolve dependency.
Praktik yang sehat
- Bind interface di provider.
- Kelompokkan binding berdasarkan domain atau modul.
- Kalau project besar, pertimbangkan provider khusus per area fitur, bukan menumpuk semuanya di
AppServiceProvider.
Facades: nyaman, tapi jangan disalahpahami
Facade terlihat seperti static call, misalnya:
use Illuminate\Support\Facades\Cache;
$value = Cache::get('profile');Tapi secara konsep, Facade bukan sekadar helper statik biasa. Ia adalah pintu masuk ke service yang dikelola container.
Kelebihan Facade
- ringkas untuk pemakaian umum,
- mudah dibaca,
- sangat cocok untuk service framework yang memang sering dipakai langsung.
Kekurangan Facade
- mudah menyamarkan dependency,
- bisa membuat class tampak “tidak punya dependency” padahal bergantung pada service global,
- kalau dipakai berlebihan, testing dan desain domain jadi kurang eksplisit.
Praktik yang aman
- gunakan Facade untuk API framework yang memang natural dipakai begitu,
- gunakan DI untuk dependency domain atau dependency yang perlu diganti dan dites,
- jangan mengandalkan Facade sebagai solusi utama untuk semua service.
Contoh hubungan ketiga konsep
Bayangkan ada PaymentGateway.
- Service Provider mendaftarkan
PaymentGatewayke implementasi tertentu. - Controller atau service menerima
PaymentGatewaylewat constructor. - Kalau di sisi lain ingin akses cepat ke
Cache, boleh pakai Facade.
class CheckoutService
{
public function __construct(
private PaymentGateway $gateway,
) {}
public function pay(array $payload): string
{
return $this->gateway->charge($payload);
}
}Di sini dependency jelas, test mudah, dan implementasi bisa diganti tanpa mengubah consumer.
Jebakan umum saat project membesar
1. Semua logika ditaruh di controller
Controller akhirnya jadi gemuk, bukan karena Laravel buruk, tapi karena tanggung jawabnya terlalu banyak.
Sinyal bahaya:
- validasi,
- transformasi data,
- akses external API,
- business logic,
- formatting response semua dilakukan di tempat yang sama.
2. Terlalu banyak Facade di domain layer
Kalau service domain langsung memanggil banyak Facade, dependency jadi samar.
Akibatnya:
- sulit dites tanpa bootstrap penuh,
- sulit melihat relasi antarservice,
- refactor jadi lebih mahal.
3. Binding disebar tanpa struktur
Saat project besar, binding yang ditaruh acak di banyak provider membuat alur aplikasi susah dibaca.
Solusi praktis:
- kelompokkan per domain,
- pakai nama contract yang jelas,
- dokumentasikan binding penting.
4. Mengandalkan auto-resolution untuk semua hal
Auto-resolution enak, tapi bukan untuk semua kasus.
Kalau constructor berubah jadi terlalu banyak dependency, itu tanda desain mulai bocor. Container memang bisa resolve, tapi bukan berarti desainnya sehat.
5. Singleton dipakai terlalu agresif
Singleton berguna, tapi kalau semua dijadikan singleton:
- state bisa bocor antarbagian,
- testing bisa rumit,
- object lifecycle jadi sulit dipahami.
Pakai singleton kalau memang objeknya layak hidup sepanjang request atau aplikasi, bukan karena terasa praktis.
6. Provider jadi tempat sampah bootstrap
Service Provider sering berubah jadi lokasi campur aduk:
- binding,
- event,
- macro,
- config transform,
- side effect lain.
Kalau terlalu besar, pecah provider per concern agar bootstrap tetap bisa dipindai cepat.
Pola yang biasanya paling stabil
Pola berikut cukup aman untuk banyak project Laravel:
- Controller: menerima request, panggil service, kembalikan response.
- Service / Action: simpan business logic.
- Contract + binding: pilih implementasi lewat container.
- Provider: daftar binding dan bootstrap.
- Facade: pakai untuk service framework yang memang wajar dipanggil langsung.
Dengan pola ini, project tetap fleksibel saat fitur bertambah.
Ringkasan cepat
- Service Container menyimpan dan resolve dependency.
- Dependency Injection membuat dependency terlihat eksplisit.
- Facades memberi akses ringkas ke service container.
- Service Providers mendaftarkan binding dan bootstrap aplikasi.
- Binding dibutuhkan saat implementasi perlu dipilih, dikonfigurasi, atau dibuat sebagai singleton/contextual dependency.
Kalau project mulai membesar, fokus utamanya bukan sekadar “bisa jalan”, tapi dependency-nya masih kebaca atau tidak. Di Laravel, container, DI, facade, dan provider baru terasa kuat kalau dipakai dengan batas yang jelas.