Laravel Service Container Di Facades Service Providers
Selesai membaca dokumentasi ini?
Kembali ke rujukan utama Laravel untuk melanjutkan topik lainnya.
Kembali ke rujukan utama Laravel untuk melanjutkan topik lainnya.
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.
Service Container adalah pusat manajemen dependency di Laravel. Di sinilah Laravel menyimpan aturan tentang bagaimana sebuah class atau interface dibuat saat dibutuhkan.
Dependency Injection adalah pola cara memberikan dependency dari luar ke class, bukan membuatnya langsung di dalam class.
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 adalah tempat Laravel mendaftarkan binding, singleton, event, dan bootstrap logic ke aplikasi.
| 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:
Mermaid di bawah ini menunjukkan alurnya secara sederhana.
Urutannya penting:
Dependency Injection dipakai saat class bergantung pada service lain dan kita ingin hubungan itu eksplisit.
use App\Repositories\UserRepository;
class UserService
{
public function __construct(
private UserRepository $users,
) {}
public function activeCount(): int
{
return $this->users->
Di sini UserService tidak membuat UserRepository sendiri. Ia hanya menyatakan bahwa ia membutuhkan repository itu.
Kalau dependency dibuat langsung di dalam class, misalnya dengan new, class jadi:
Binding dibutuhkan saat Laravel tidak bisa atau tidak seharusnya menebak cara membuat object.
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.
Misalnya client HTTP yang butuh base URL, token, atau opsi tertentu.
$this->app->singleton(ReportClient::class, function () {
return new ReportClient(config('services.report.url'));
});Kalau satu instance ingin dipakai ulang selama request yang sama.
$this->app->singleton(MailerFactory::class, fn () => new MailerFactory());Kalau implementasi yang dipakai berbeda tergantung class pemakainya.
$this->app->when(AdminReportController::class)
->needs(ReportRenderer::class)
->give(AdminReportRenderer::class);Binding sering tidak perlu kalau:
Laravel cukup pintar untuk banyak kasus. Jadi binding bukan default untuk semua hal. Binding dipakai saat ada kontrak, variasi implementasi, konfigurasi, atau lifecycle khusus.
Service Provider adalah titik masuk resmi untuk menaruh binding dan bootstrap logic.
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:
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.
Secara konsep, provider adalah tempat menyusun bootstrap aplikasi. Saat aplikasi start:
register() dijalankan untuk binding.boot() dijalankan untuk inisialisasi lanjutan.AppServiceProvider.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.
Bayangkan ada PaymentGateway.
PaymentGateway ke implementasi tertentu.PaymentGateway lewat constructor.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.
Controller akhirnya jadi gemuk, bukan karena Laravel buruk, tapi karena tanggung jawabnya terlalu banyak.
Sinyal bahaya:
Kalau service domain langsung memanggil banyak Facade, dependency jadi samar.
Akibatnya:
Saat project besar, binding yang ditaruh acak di banyak provider membuat alur aplikasi susah dibaca.
Solusi praktis:
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.
Singleton berguna, tapi kalau semua dijadikan singleton:
Pakai singleton kalau memang objeknya layak hidup sepanjang request atau aplikasi, bukan karena terasa praktis.
Service Provider sering berubah jadi lokasi campur aduk:
Kalau terlalu besar, pecah provider per concern agar bootstrap tetap bisa dipindai cepat.
Pola berikut cukup aman untuk banyak project Laravel:
Dengan pola ini, project tetap fleksibel saat fitur bertambah.
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.