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