Otomatik Bağlama (Autowiring)

Otomatik bağlama (Autowiring), kurucuya (constructor) ve diğer metotlara gerekli servisleri otomatik olarak aktarabilen harika bir özelliktir, böylece onları hiç yazmamıza gerek kalmaz. Size çok zaman kazandırır.

Bu sayede servis tanımlarını yazarken argümanların büyük çoğunluğunu atlayabiliriz. Yerine:

services:
	articles: Model\ArticleRepository(@database, @cache.storage)

Sadece şunu yazmak yeterlidir:

services:
	articles: Model\ArticleRepository

Otomatik bağlama tiplere göre yönlendirilir, bu yüzden çalışması için ArticleRepository sınıfının yaklaşık olarak şöyle tanımlanması gerekir:

namespace Model;

class ArticleRepository
{
	public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
	{}
}

Otomatik bağlamanın kullanılabilmesi için, her tip için konteynerde tam olarak bir servis olmalıdır. Eğer birden fazla olsaydı, otomatik bağlama hangisini aktaracağını bilemez ve bir istisna fırlatırdı:

services:
	mainDb: PDO(%dsn%, %user%, %password%)
	tempDb: PDO('sqlite::memory:')
	articles: Model\ArticleRepository  # İSTİSNA FIRLATIR, hem mainDb hem de tempDb uyar

Çözüm, ya otomatik bağlamayı atlamak ve servis adını açıkça belirtmek (yani articles: Model\ArticleRepository(@mainDb)) ya da daha akıllıca olanı, servislerden birinin otomatik bağlanmasını kapatmak veya ilk servisi önceliklendirmektir.

Otomatik Bağlamayı Kapatma

Bir servisin otomatik bağlanmasını autowired: no seçeneğiyle kapatabiliriz:

services:
	mainDb: PDO(%dsn%, %user%, %password%)

	tempDb:
		create: PDO('sqlite::memory:')
		autowired: false               # tempDb servisi otomatik bağlamadan çıkarıldı

	articles: Model\ArticleRepository  # bu yüzden kurucuya mainDb aktarılır

articles servisi, kurucuya aktarılabilecek iki uygun PDO tipi servis (yani mainDb ve tempDb) olduğu için istisna fırlatmaz, çünkü yalnızca mainDb servisini görür.

Nette'deki otomatik bağlama yapılandırması, Symfony'den farklı çalışır; burada autowire: false seçeneği, verilen servisin kurucu argümanları için otomatik bağlamanın kullanılmaması gerektiğini belirtir. Nette'de otomatik bağlama her zaman kullanılır, ister kurucu argümanları için ister başka herhangi bir metot için olsun. autowired: false seçeneği, verilen servisin örneğinin otomatik bağlama yoluyla hiçbir yere aktarılmaması gerektiğini belirtir.

Otomatik Bağlama Önceliği

Aynı tipten birden fazla servisimiz varsa ve bunlardan birinde autowired seçeneğini belirtirsek, bu servis tercih edilen olur:

services:
	mainDb:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO    # tercih edilen olur

	tempDb:
		create: PDO('sqlite::memory:')

	articles: Model\ArticleRepository

articles servisi, iki uygun PDO tipi servis (yani mainDb ve tempDb) olduğu için istisna fırlatmaz, ancak tercih edilen servisi, yani mainDb'yi kullanır.

Servis Dizileri

Otomatik bağlama, belirli bir tipteki servis dizilerini de aktarabilir. PHP'de dizi öğelerinin tipini yerel olarak yazılamadığı için, array tipine ek olarak ClassName[] formatında öğe tipini içeren bir phpDoc yorumu eklemek gerekir:

namespace Model;

class ShipManager
{
	/**
	 * @param Shipper[] $shippers
	 */
	public function __construct(array $shippers)
	{}
}

DI konteyneri daha sonra otomatik olarak ilgili tipe karşılık gelen servis dizisini aktarır. Otomatik bağlanması kapalı olan servisleri atlar.

Yorumdaki tip array<int, Class> veya list<Class> şeklinde de olabilir. Eğer phpDoc yorumunun şeklini etkileyemiyorsanız, servis dizisini doğrudan yapılandırmada typed() kullanarak aktarabilirsiniz.

Skaler Argümanlar

Otomatik bağlama yalnızca nesneleri ve nesne dizilerini yerleştirebilir. Skaler argümanları (örn. karakter dizileri, sayılar, booleanlar) yapılandırmada yazarız. Alternatif olarak, skaler değeri (veya birden fazla değeri) bir nesne şeklinde kapsülleyen bir ayarlar nesnesi oluşturmaktır ve bu nesne daha sonra tekrar otomatik bağlama ile aktarılabilir.

class MySettings
{
	public function __construct(
		// readonly PHP 8.1'den itibaren kullanılabilir
		public readonly bool $value,
	)
	{}
}

Yapılandırmaya ekleyerek ondan bir servis oluşturursunuz:

services:
	- MySettings('any value')

Tüm sınıflar daha sonra onu otomatik bağlama ile talep eder.

Otomatik Bağlamayı Daraltma

Bireysel servisler için otomatik bağlama yalnızca belirli sınıflara veya arayüzlere daraltılabilir.

Normalde otomatik bağlama, servisi, tipine uyduğu her metot parametresine aktarır. Daraltma, servisin onlara aktarılması için metot parametrelerinde belirtilen tiplerin uyması gereken koşulları belirlediğimiz anlamına gelir.

Bunu bir örnekle gösterelim:

class ParentClass
{}

class ChildClass extends ParentClass
{}

class ParentDependent
{
	function __construct(ParentClass $obj)
	{}
}

class ChildDependent
{
	function __construct(ChildClass $obj)
	{}
}

Eğer hepsini servis olarak kaydetseydik, otomatik bağlama başarısız olurdu:

services:
	parent: ParentClass
	child: ChildClass
	parentDep: ParentDependent  # İSTİSNA FIRLATIR, hem parent hem de child servisleri uyar
	childDep: ChildDependent    # otomatik bağlama kurucuya child servisini aktarır

parentDep servisi Multiple services of type ParentClass found: parent, child istisnasını fırlatır, çünkü kurucusuna hem parent hem de child servisleri uyar ve otomatik bağlama hangisini seçeceğine karar veremez.

Bu nedenle, child servisi için otomatik bağlanmasını ChildClass tipine daraltabiliriz:

services:
	parent: ParentClass
	child:
		create: ChildClass
		autowired: ChildClass   # 'autowired: self' de yazılabilir

	parentDep: ParentDependent  # otomatik bağlama kurucuya parent servisini aktarır
	childDep: ChildDependent    # otomatik bağlama kurucuya child servisini aktarır

Şimdi parentDep servisi kurucusuna parent servisi aktarılır, çünkü şimdi tek uygun nesne odur. child servisini otomatik bağlama artık oraya aktarmaz. Evet, child servisi hala ParentClass tipindedir, ancak parametre tipi için verilen daraltma koşulu artık geçerli değildir, yani ParentClass 'ın ChildClass 'ın üst tipi olduğu geçerli değildir.

child servisi için autowired: ChildClass yerine autowired: self de yazılabilirdi, çünkü self geçerli servisin sınıfı için bir yer tutucu tanımdır.

autowired anahtarında, bir dizi olarak birkaç sınıf veya arayüz de belirtilebilir:

autowired: [BarClass, FooInterface]

Örneği bir arayüzle daha tamamlamayı deneyelim:

interface FooInterface
{}

interface BarInterface
{}

class ParentClass implements FooInterface
{}

class ChildClass extends ParentClass implements BarInterface
{}

class FooDependent
{
	function __construct(FooInterface $obj)
	{}
}

class BarDependent
{
	function __construct(BarInterface $obj)
	{}
}

class ParentDependent
{
	function __construct(ParentClass $obj)
	{}
}

class ChildDependent
{
	function __construct(ChildClass $obj)
	{}
}

Eğer child servisini hiçbir şekilde sınırlamazsak, tüm FooDependent, BarDependent, ParentDependent ve ChildDependent sınıflarının kurucularına uyar ve otomatik bağlama onu oraya aktarır.

Ancak otomatik bağlanmasını autowired: ChildClass (veya self) kullanarak ChildClass'a daraltırsak, otomatik bağlama onu yalnızca ChildDependent kurucusuna aktarır, çünkü ChildClass tipinde bir argüman gerektirir ve ChildClass 'ın ChildClass tipinde olduğu geçerlidir. Diğer parametrelerde belirtilen başka hiçbir tip ChildClass'ın üst tipi değildir, bu yüzden servis aktarılmaz.

Eğer onu autowired: ParentClass kullanarak ParentClass'a sınırlarsak, otomatik bağlama onu tekrar ChildDependent kurucusuna (çünkü gerekli ChildClass, ParentClass'ın üst tipidir) ve yeni olarak ParentDependent kurucusuna aktarır, çünkü gerekli ParentClass tipi de uygundur.

Eğer onu FooInterface'e sınırlarsak, hala ParentDependent (gerekli ParentClass, FooInterface'in üst tipidir) ve ChildDependent'e otomatik bağlanır, ancak ek olarak FooDependent kurucusuna da bağlanır, ancak BarDependent'e bağlanmaz, çünkü BarInterface, FooInterface'in üst tipi değildir.

services:
	child:
		create: ChildClass
		autowired: FooInterface

	fooDep: FooDependent        # otomatik bağlama kurucuya child aktarır
	barDep: BarDependent        # İSTİSNA FIRLATIR, hiçbir servis uymuyor
	parentDep: ParentDependent  # otomatik bağlama kurucuya child aktarır
	childDep: ChildDependent    # otomatik bağlama kurucuya child aktarır
versiyon: 3.x