LARAVEL SERVICE CONTAINER NEDİR?

Pek çok geliştirici Laravel Service Container'ı "sadece bir dependency injection aracı" olarak tanır — oysa bu, buzdağının görünen kısmıdır. Container, framework'ün hangi sınıfı nereye, nasıl ve hangi koşulda çözeceğini belirleyen merkezi karar mekanizmasıdır. Binding, contextual binding, singleton, interface çözümleme ve service provider'ların orkestra şefi gibi çalışan bu yapıyı anlamadan Laravel'in iç mantığını da kavramak zordur. Bu yanılgıyı netleştirmek, kodunuzun test edilebilirliğini ve modüler tasarımını doğrudan etkiler.
1. Service Container Aslında Ne Yapar?
Service Container, sınıf bağımlılıklarını otomatik olarak çözen ve nesne yaşam döngüsünü yöneten bir IoC (Inversion of Control) konteyneridir. Bir controller'a tip-ipucu (type-hint) ile sınıf eklediğinizde, Laravel arka planda reflection kullanarak gerekli nesneyi container üzerinden inşa eder.
Fakat container'ı yalnızca DI mekanizması saymak şu özellikleri gözden kaçırmak demektir:
- Binding kayıtları: Arayüzleri somut sınıflara haritalar.
- Singleton & scoped bağlamalar: Aynı request boyunca tek örnek garantisi.
- Contextual binding: Aynı arayüzü farklı sınıflara farklı somutlarla çözer.
- Tagged services: Birden fazla servisi etiketleyip toplu çözümleme.
- Method injection: Sadece constructor değil, metot parametrelerinde de otomatik çözümleme.
2. Binding Türleri ve Aralarındaki Fark
Container'a bir bağlama tanımlamak, "şu anahtarı istediğinde sana şu nesneyi vereceğim" demektir. Laravel üç temel bağlama türü sunar:
bind()— Her çözümlemede yeni örnek üretir.singleton()— Uygulama yaşam döngüsü boyunca tek örnek döner.instance()— Önceden oluşturulmuş nesneyi container'a iletir.
Örnek bir binding tanımı şu şekilde yazılabilir:
$this->app->bind(PaymentGateway::class, StripeGateway::class);
Burada PaymentGateway arayüzü her istendiğinde container StripeGateway örneği döndürür. Aynı bağlamayı singleton ile tanımlasaydınız, her çözümlemede aynı nesne dönerdi — özellikle veritabanı bağlantısı veya HTTP client gibi pahalı kaynaklarda kritik bir tercihtir.

3. Contextual Binding: Aynı Arayüz, Farklı Bağlam
Contextual binding, Laravel'in en az bilinen ama en güçlü özelliklerinden biridir. Diyelim ki Logger arayüzünüz var ve OrderController dosya log'u, PaymentController ise Slack log'u istiyor. İki controller da aynı arayüze bağımlıyken hangisinin hangi somutu alacağını şöyle belirlersiniz:
$this->app->when(OrderController::class)->needs(Logger::class)->give(FileLogger::class);
Bu, "OrderController bir Logger isterse FileLogger ver" anlamına gelir. Aynı arayüzü farklı yerlerde farklı şekilde çözmek, klasik DI konteynerlerinde genellikle ek konfigürasyon gerektirir; Laravel bunu okunabilir bir DSL ile sunar.
4. Service Provider ile Container İlişkisi
Service Provider'lar, container'a bağlamaları kaydeden merkezi yapı taşlarıdır. register() metodu yalnızca binding tanımlar — bu aşamada başka servisleri çözmek riskli olabilir çünkü diğer provider'lar henüz yüklenmemiş olabilir. boot() metodu ise tüm provider'lar register edildikten sonra çalışır ve burada güvenle diğer servislere erişebilirsiniz.
Bu ayrım, framework'ün açılış sırasını öngörülebilir hale getirir. Kendi paketinizi yazarken bir ServiceProvider sınıfı oluşturup register() içinde container'a bağlamalarınızı eklemek, Laravel'in genişletme arayüzünün önerilen yoludur; daha ayrıntılı kullanım örnekleri için container kullanım kılavuzunu incelemek faydalı olur.
5. Otomatik Çözümleme (Auto-Resolution)
Laravel, type-hint edilen sınıfları herhangi bir manuel binding olmadan da çözebilir. Eğer bağımlılık somut bir sınıfsa, container reflection ile constructor parametrelerini bakarak nesneyi inşa eder. Manuel binding sadece şu durumlarda gerekir:
- Bir arayüzü somut sınıfa eşlemek istediğinizde
- Aynı arayüze contextual bağlama vermek gerektiğinde
- Karmaşık constructor parametreleri (primitive değerler) söz konusuyken
- Singleton davranışı istediğinizde
6. Container'ı Test Senaryolarında Kullanmak
Test sırasında bir servisi sahte (mock) bir örnekle değiştirmek istediğinizde container devreye girer. $this->app->instance(MailService::class, $mock); satırı, test boyunca MailService her istendiğinde sizin sağladığınız mock'u dönecektir. Bu, gerçek e-posta gönderimini, dış API çağrılarını ve yan etkileri izole etmenin en temiz yoludur.
Laravel ile DI ve container mekanizmalarını daha derinlemesine öğrenmek için PHP Laravel eğitimi içeriklerinden yararlanabilirsiniz; özellikle service provider yapısının kurumsal projelerdeki rolünü adım adım ele alır.
7. Tagged Services ve İleri Senaryolar
Birden fazla servisi ortak bir etiket altında toplayıp tek seferde çözmek bazen kaçınılmazdır. Örneğin bir rapor sisteminde farklı export servislerini şöyle etiketleyebilirsiniz:
$this->app->tag([PdfExporter::class, ExcelExporter::class, CsvExporter::class], 'exporters');
Sonra ihtiyaç duyduğunuzda $this->app->tagged('exporters') ile tüm etiketli servisleri bir iterable olarak alırsınız. Bu desen, plugin mimarileri ve strategy pattern uygulamaları için son derece kullanışlıdır.

8. Yaygın Hatalar ve Doğru Pratikler
Container'ı kullanırken karşılaşılan tipik tuzaklar vardır. App::make() veya facade üzerinden manuel çözümleme (service locator anti-pattern), constructor injection yerine tercih edildiğinde test edilebilirliği zayıflatır. Mümkün olduğunca tip-ipucu yoluyla otomatik çözümlemeye güvenmek daha sürdürülebilir kod üretir.
Aynı şekilde her servisi singleton yapmak da yanlıştır — state taşıyan ama isolation gereken servislerde bu, beklenmedik yan etkilere yol açar. Container'ı yalnızca bir araç olarak değil, mimari kararlarınızın yansıdığı bir konfigürasyon katmanı olarak görmek, Laravel projelerinde uzun vadeli kod kalitesini belirleyen en kritik tercihlerden biridir. Container'ın gerçek gücünü kavradığınızda, framework'ün size sunduğu esneklik aniden çok daha geniş bir alana yayılır.
Sıkça Sorulan Sorular
Service Container sadece dependency injection için mi kullanılır?
Hayır. DI önemli bir kullanım alanıdır ancak container aynı zamanda binding kayıtları, singleton yönetimi, contextual binding, tagged services ve service provider entegrasyonu gibi çok katmanlı sorumluluklar taşır. Onu yalnızca DI aracı olarak görmek, Laravel mimarisinin önemli bir kısmını gözden kaçırmak anlamına gelir.
bind ile singleton arasındaki temel fark nedir?
bind her çözümleme isteğinde yeni bir nesne örneği üretirken, singleton uygulama yaşam döngüsü boyunca yalnızca bir kez nesne oluşturur ve sonraki tüm isteklere aynı örneği döndürür. Veritabanı bağlantısı veya HTTP client gibi pahalı kaynaklar için singleton tercih edilir.
Contextual binding ne zaman kullanılmalıdır?
Aynı arayüzün farklı sınıflarda farklı somut uygulamalarla çözülmesi gerektiğinde kullanılır. Örneğin bir Logger arayüzü, OrderController için dosya log'u, PaymentController için Slack log'u döndürmeli ise contextual binding ile her bağlam ayrı ayrı tanımlanır.
Service Provider içinde register ve boot metotları neden ayrı?
register metodu yalnızca bağlamaları kaydeder; bu aşamada diğer servisler henüz yüklenmemiş olabilir. boot metodu tüm provider'lar register edildikten sonra çalışır ve burada diğer servislere güvenle erişilebilir. Bu ayrım framework'ün açılış sırasını öngörülebilir kılar.
Otomatik çözümleme için her zaman binding tanımlamak zorunda mıyım?
Hayır. Somut sınıflar için type-hint yeterlidir; Laravel reflection ile bağımlılıkları otomatik çözer. Manuel binding yalnızca arayüz-somut eşlemesi, singleton davranışı, contextual binding veya primitive parametre içeren constructor'lar gibi özel durumlarda gereklidir.
Test sırasında container nasıl kullanılır?
Bir servisi mock ile değiştirmek için app()->instance(Class::class, $mock) satırı kullanılır. Bu, test süresince ilgili sınıf her istendiğinde sağlanan sahte nesnenin döndürülmesini sağlar. Gerçek API çağrıları, e-posta gönderimi gibi yan etkileri izole etmenin en temiz yoludur.
Tagged services hangi durumlarda işe yarar?
Birden fazla servisin ortak bir etiket altında toplanıp tek seferde çözülmesi gerektiğinde kullanılır. Plugin mimarileri, çoklu export servisleri, strategy pattern uygulamaları gibi senaryolarda aynı kategorideki tüm servislere iterable olarak erişmek için son derece kullanışlıdır.
App::make ile otomatik çözümleme arasında tercih yaparken nelere dikkat etmeliyim?
App::make manuel çözümlemedir ve service locator anti-pattern'ine yaklaşır; test edilebilirliği zayıflatır. Mümkün olduğunda constructor veya method injection yoluyla type-hint ile otomatik çözümlemeyi tercih etmek daha temiz ve sürdürülebilir kod üretir.


