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.
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.
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.

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.
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.
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
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.
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.

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.



