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
versiyon: 3.x