Laravel'in Service Container'ı, sınıflar ve arayüzler arasındaki bağımlılıkları otomatik olarak çözmek için kullanılan bir IoC (Inversion of Control) mekanizmasıdır. Bu yapı sayesinde nesneleri manuel olarak oluşturmak yerine framework'ün onları enjekte etmesini sağlayabiliriz. Container'a binding tanımlamaları nasıl yapılır, singleton ve bind arasındaki farklar nelerdir, ve test ortamlarında bu özellikleri nasıl etkili kullanabiliriz? Sizce en temiz yaklaşım hangisi?
Laravel'de Service Container nedir ve bağımlılık enjeksiyonu nasıl yönetilir?
👁️ 87 görüntüleme💬 2 cevap❤️ 0 beğeni
2 Cevap
Laravel’de Service Container’ı ilk kez projemde bir “payment gateway” entegrasyonu yaparken fark ettim. Ödeme sınıfını doğrudan `new PaymentService()` ile çağırmak yerine, Container’a bir binding tanımladım:
```php
$this->app->bind(PaymentGateway::class, function ($app) {
return new StripeGateway(config('services.stripe.key'));
});
```
Bu sayede controller’da sadece `PaymentGateway $gateway` tipini isteyince Laravel otomatik olarak StripeGateway’i enjekte etti. Kanka, bu yaklaşım kodu çok daha temiz hâle getiriyor; sınıflar arası sıkı bağları kırıp, “kodu nerede değiştirirsem aynı yerden etkilenir” rahatlığını yakalıyorum.
Singleton ve bind arasındaki farkı da bir test ortamında çığır açtı. Bind her çağrıda yeni bir instance üretirken, singleton aynı instance’ı bir kez oluşturup her yerden paylaşıyor. Örneğin, bir “logger” servisini singleton olarak tanımlamak, test sırasında aynı log dosyasına birden çok yazı düşmesini önledi:
```php
$this->app->singleton(LoggerInterface::class, function () {
return new FileLogger(storage_path('logs/app.log'));
});
```
Valla, bind kullandığımda her test metodunda yeni bir logger yaratıyordu ve loglar karışıyordu. Bu yüzden bence singleton, durum‑değişmeyen ve paylaşımlı hizmetlerde tercih edilmeli.
Test ortamında da Container’ı manipüle etmek çok pratik. `phpunit.xml` içinde `APP_ENV=testing` ayarıyla, `setUp` metodunda mock binding’leri yapıp gerçek servisleri izole edebiliyoruz:
```php
$this->app->instance(PaymentGateway::class, Mockery::mock(PaymentGateway::class));
```
Böylece gerçek Stripe API çağrısı yapmadan, sadece beklenen davranışı doğrulayabiliyoruz. En temiz yaklaşım, **interface‑based binding** yapıp, ihtiyaç duyulan yerde sadece arayüzü tip olarak istemek. Böylece gerçek implementasyon değişse bile (Stripe → PayPal) kodda tek bir satır değişikliği kalıyor.
Sonuçta, Container’ı sadece “bağla ve unut” gibi kullanmak yerine, yaşam döngüsünü (bind vs singleton) ve test senaryolarını da düşünerek yapılandırmak, projenin ölçeklenebilirliğini ciddi ölçüde artırıyor. Bence bu yöntemi benimseyen ekipler, uzun vadede daha az hatayla, daha hızlı geliştirme yapabiliyor.
Aynen, ben de çoğu zaman `app()->singleton()` ile tek bir örnek tutup, interface‑i `bind()` ile birden çok implemantasyonla değiştiriyorum; testlerde ise `Mockery` ile container’a mock bind edip, gerçek servisi geçici olarak değiştiriyorum, bu sayede bağımlılıkları izole edip hızlıca test edebiliyoruz; bence en temiz yol, interface‑tabanlı binding yapıp, sadece ihtiyaç duyulan yerde `resolve()` ya da tip‑hint üzerinden otomatik enjeksiyon kullandığında, kod daha okunur ve sürdürülebilir oluyor.