Bağımlılıkların Aktarılması
Argümanlar, ya da DI terminolojisiyle “bağımlılıklar”, sınıflara başlıca şu yollarla aktarılabilir:
- Yapıcı enjeksiyonu (constructor injection)
- Metot enjeksiyonu (setter injection denir)
- Özellik enjeksiyonu (property injection)
inject*()metodu ya da#[Inject]attribute'u ile
Her seçeneği somut örneklerle gösterelim.
Yapıcı Enjeksiyonu
Bağımlılıklar, nesne örneklenirken yapıcı argümanları olarak verilir:
class MyClass
{
private Cache $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
$obj = new MyClass($cache);
Bu yaklaşım, sınıfın çalışması için kesinlikle gereken zorunlu bağımlılıklar için uygundur; çünkü onlar olmadan örnek oluşturulamaz.
PHP 8.0'dan beri, işlevsel olarak eşdeğer olan daha kısa bir yazım (constructor property promotion) kullanabiliriz:
// PHP 8.0
class MyClass
{
public function __construct(
private Cache $cache,
) {
}
}
PHP 8.1'den beri bir özellik readonly bayrağıyla işaretlenebilir; bu, özelliğin değerinin başlatmadan
sonra değişmeyeceğini bildirir:
// PHP 8.1
class MyClass
{
public function __construct(
private readonly Cache $cache,
) {
}
}
DI container, bağımlılıkları yapıcıya autowiring ile otomatik olarak aktarır. Bu yolla verilemeyen argümanlar (örneğin dizeler, sayılar, boolean değerler) yapılandırmada belirtilir.
Constructor Hell
Constructor hell terimi, bir alt sınıfın, yapıcısı bağımlılık isteyen bir üst sınıftan türediği ve alt sınıfın da kendi bağımlılıklarını istediği durumu anlatır. O zaman üst sınıfın bağımlılıklarını da alıp aktarması gerekir:
abstract class BaseClass
{
private Cache $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
final class MyClass extends BaseClass
{
private Database $db;
// ⛔ CONSTRUCTOR HELL
public function __construct(Cache $cache, Database $db)
{
parent::__construct($cache);
$this->db = $db;
}
}
Sorun, BaseClass sınıfının yapıcısını değiştirmek istediğimizde, örneğin yeni bir bağımlılık
eklendiğinde ortaya çıkar. O zaman alt sınıfların tüm yapıcılarını da değiştirmek gerekir. Bu da böyle bir
değişikliği cehenneme çevirir.
Bu nasıl önlenir? Çözüm, kompozisyonu kalıtıma yeğlemektir.
Yani kodu farklı tasarlarız. Soyut Base*
sınıflarından kaçınacağız. MyClass, belirli bir işlevi BaseClass sınıfından türeyerek
edinmek yerine, bu işlevi bağımlılık olarak alacak:
final class SomeFunctionality
{
private Cache $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
final class MyClass
{
private SomeFunctionality $sf;
private Database $db;
public function __construct(SomeFunctionality $sf, Database $db) // ✅
{
$this->sf = $sf;
$this->db = $db;
}
}
Setter Enjeksiyonu
Bağımlılıklar, onları private bir özellikte saklayan bir metot çağrılarak verilir. Bu metotların yaygın adlandırma
uzlaşımı set*() biçimidir, bu yüzden onlara setter denir; ama elbette başka türlü de
adlandırılabilirler.
class MyClass
{
private Cache $cache;
public function setCache(Cache $cache): void
{
$this->cache = $cache;
}
}
$obj = new MyClass;
$obj->setCache($cache);
Bu yaklaşım, sınıfın çalışması için zorunlu olmayan isteğe bağlı bağımlılıklar için uygundur; çünkü nesnenin bağımlılığı gerçekten alacağı (yani çağıranın metodu çağıracağı) garanti değildir.
Aynı zamanda bu yöntem, bağımlılığı değiştirmek için setter'ın defalarca çağrılmasına olanak tanır. Bu
istenmiyorsa metoda bir denetim ekleyin ya da PHP 8.1'den beri $cache özelliğini readonly bayrağıyla
işaretleyin.
class MyClass
{
private Cache $cache;
public function setCache(Cache $cache): void
{
if (isset($this->cache)) {
throw new RuntimeException('The dependency has already been set');
}
$this->cache = $cache;
}
}
Setter çağrısı, DI container yapılandırmasında setup anahtarında tanımlanır. Burada da bağımlılıkların autowiring ile otomatik aktarımı kullanılır:
services:
- create: MyClass
setup:
- setCache
Özellik Enjeksiyonu
Bağımlılıklar, doğrudan bir üye özelliğe yazılarak verilir:
class MyClass
{
public Cache $cache;
}
$obj = new MyClass;
$obj->cache = $cache;
Bu yöntem uygunsuz sayılır; çünkü üye özelliğin public olarak bildirilmesi gerekir. Sonuç olarak,
aktarılan bağımlılığın gerçekten gereken türde olduğunu güvence altına alma denetimini yitiririz (bu özellikle PHP
7.4'ün özellik tür bildirimlerinden önce geçerliydi) ve yeni atanan bir bağımlılığa özel mantıkla tepki verme,
örneğin sonradan değiştirilmesini önleme olanağını kaybederiz. Aynı zamanda özellik, sınıfın public API'sinin bir
parçası hâline gelir; bu da istenmeyebilir.
Özellik ataması, DI container yapılandırmasında setup bölümünde tanımlanır:
services:
- create: MyClass
setup:
- $cache = @\Cache
Inject
Önceki üç yaklaşım tüm nesne yönelimli dillerde genel olarak geçerliyken, inject*() metotlarıyla ya da
#[Inject] attribute'uyla enjeksiyon tipik olarak Nette presenter'larında kullanılır ve orada varsayılan olarak
etkindir; başka herhangi bir servis inject: true ile bunu açabilir.
Bunlar ayrı bir bölümde ele alınıyor.
Hangi Yöntemi Seçmeli?
- Yapıcı, sınıfın çalışması için kesinlikle gereken zorunlu bağımlılıklar için uygundur.
- Setter ise tersine, isteğe bağlı ya da sonradan değiştirilmesi gerekebilecek bağımlılıklar için uygundur.
- Public özellikler genellikle önerilmez.