Bağımlılıkların İletilmesi
Argümanlar veya DI terminolojisinde “bağımlılıklar”, sınıflara şu ana yollarla iletilebilir:
- yapıcı ile iletme
- metot ile iletme (sözde setter)
- değişken ayarlayarak
- inject metodu, anotasyonu veya niteliği ile
Şimdi farklı varyantları belirli örneklerle göstereceğiz.
Yapıcı ile İletme
Bağımlılıklar, nesne oluşturma anında yapıcı argümanları olarak iletilir:
class MyClass
{
private Cache $cache;
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
$obj = new MyClass($cache);
Bu form, sınıfın işlevi için mutlaka ihtiyaç duyduğu zorunlu bağımlılıklar için uygundur, çünkü onlarsız örnek oluşturulamaz.
PHP 8.0'dan itibaren, 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 itibaren, değişkenin içeriğinin artık değişmeyeceğini bildiren readonly
bayrağıyla
işaretlenebilir:
// PHP 8.1
class MyClass
{
public function __construct(
private readonly Cache $cache,
) {
}
}
DI konteyneri, yapıcıya bağımlılıkları autowiring kullanarak otomatik olarak iletir. Bu şekilde iletilemeyen argümanlar (örneğin dizeler, sayılar, boolean'lar) yapılandırmada belirtiriz.
Constructor Hell
Constructor hell terimi, bir alt sınıfın, yapıcısı bağımlılıklar gerektiren bir üst sınıftan miras aldığı ve aynı zamanda alt sınıfın da bağımlılıklar gerektirdiği durumu ifade eder. Bu durumda, üst sınıfın bağımlılıklarını da alıp iletmesi 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 ortaya çıkar, örneğin yeni bir
bağımlılık eklendiğinde. O zaman tüm alt sınıfların yapıcılarını da değiştirmek gerekir. Bu da böyle bir
değişikliği cehenneme çevirir.
Bundan nasıl kaçınılır? Çözüm, kalıtım yerine kompozisyonu tercih etmektir.
Yani, kodu farklı tasarlayacağız. Soyut Base*
sınıflarından kaçınacağız. MyClass
'ın belirli bir işlevselliği BaseClass
'tan miras alarak elde
etmesi yerine, bu işlevselliği bir bağımlılık olarak almasını sağlayacağız:
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 ile İletme
Bağımlılıklar, onları özel bir değişkende saklayan bir metot çağrılarak iletilir. Bu metotları adlandırmak için
yaygın bir kural set*()
şeklindedir, bu yüzden onlara setter denir, ancak elbette başka herhangi bir şekilde
adlandırılabilirler.
class MyClass
{
private Cache $cache;
public function setCache(Cache $cache): void
{
$this->cache = $cache;
}
}
$obj = new MyClass;
$obj->setCache($cache);
Bu yöntem, sınıfın işlevi için gerekli olmayan isteğe bağlı bağımlılıklar için uygundur, çünkü nesnenin bağımlılığı gerçekten alacağı garanti edilmez (yani kullanıcının metodu çağıracağı).
Aynı zamanda bu yöntem, setter'ı tekrar tekrar çağırmaya ve böylece bağımlılığı değiştirmeye izin verir. Bu
istenmiyorsa, metoda bir kontrol ekleriz veya PHP 8.1'den itibaren $cache
özelliğini readonly
bayrağıyla işaretleriz.
class MyClass
{
private Cache $cache;
public function setCache(Cache $cache): void
{
if (isset($this->cache)) {
throw new RuntimeException('Bağımlılık zaten ayarlandı');
}
$this->cache = $cache;
}
}
Setter çağrısını DI konteyneri yapılandırmasında setup anahtarında tanımlarız. Burada da autowiring kullanarak otomatik bağımlılık iletimi kullanılır:
services:
- create: MyClass
setup:
- setCache
Değişken Ayarlayarak
Bağımlılıklar, doğrudan üye değişkene yazılarak iletilir:
class MyClass
{
public Cache $cache;
}
$obj = new MyClass;
$obj->cache = $cache;
Bu yöntem uygunsuz kabul edilir, çünkü üye değişken public
olarak bildirilmelidir. Ve dolayısıyla,
iletilen bağımlılığın gerçekten verilen türde olacağını kontrol edemeyiz (PHP 7.4 öncesinde geçerliydi) ve yeni
atanan bağımlılığa kendi kodumuzla tepki verme, örneğin sonraki değişikliği engelleme yeteneğini kaybederiz. Aynı
zamanda değişken, sınıfın genel arayüzünün bir parçası haline gelir, bu da istenmeyebilir.
Değişken ayarını DI konteyneri yapılandırmasında setup bölümünde tanımlarız:
services:
- create: MyClass
setup:
- $cache = @\Cache
Inject
Önceki üç yöntem tüm nesne yönelimli dillerde genel olarak geçerliyken, inject metodu, anotasyonu veya niteliği ile enjekte etme, Nette'deki presenter'lara özgüdür. Bunlar ayrı bir bölüm içinde ele alınmaktadır.
Hangi Yöntemi Seçmeli?
- yapıcı, sınıfın işlevi için mutlaka ihtiyaç duyduğu zorunlu bağımlılıklar için uygundur
- setter ise isteğe bağlı bağımlılıklar veya daha sonra değiştirilebilme olasılığı olan bağımlılıklar için uygundur
- public değişkenler uygun değildir