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