Autowiring
Autowiring, gereken servisleri yapıcıya ve diğer metotlara otomatik olarak aktaran harika bir özelliktir; böylece onları açıkça belirtmemiz gerekmez. Size bolca zaman kazandırır.
Bu sayede servis tanımlarını yazarken argümanların büyük çoğunluğunu atlayabiliriz. Şunun yerine:
services:
articles: Model\ArticleRepository(@database, @cache.storage)
Yalnızca şunu yazın:
services:
articles: Model\ArticleRepository
Autowiring türlerle yönlendirilir; bu yüzden çalışması için ArticleRepository sınıfının kabaca şöyle
tanımlanması gerekir:
namespace Model;
class ArticleRepository
{
public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
{}
}
Autowiring asla servis adlarını kullanmaz. Yalnızca PHP'nin tür sistemiyle yönlendirilir; dolayısıyla bir sınıfın, uyguladığı arayüzleri ve türediği sınıfları da karşıladığını bilir. Bu sayede servisin adı yalnızca yardımcı bir tanımlayıcıdır ve onu yeniden adlandırmak uygulamada hiçbir şeyi bozmaz.
Autowiring'i kullanabilmek için container'da her türden tam olarak bir servis bulunmalıdır. Daha fazlası olsaydı autowiring hangisini aktaracağını bilemez ve istisna fırlatırdı:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb: PDO('sqlite::memory:')
articles: Model\ArticleRepository # İSTİSNA FIRLATIR, hem mainDb hem tempDb uyuyor
Bir çözüm, autowiring'i atlayıp servis adını açıkça belirtmektir (örneğin
articles: Model\ArticleRepository(@mainDb)). Ancak daha elverişli bir yaklaşım, ya servislerden biri için
autowiring'i kapatmak ya da bir servisi diğerlerine yeğlemektir.
Autowiring'i Kapatma
Bir servis için autowiring'i autowired: false seçeneğiyle kapatabiliriz:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
create: PDO('sqlite::memory:')
autowired: false # tempDb servisi autowiring'den çıkarılır
articles: Model\ArticleRepository # bu yüzden yapıcıya mainDb aktarılır
articles servisi, yapıcı için uyan iki PDO servisinin (mainDb ve tempDb)
bulunduğuna dair istisna fırlatmaz; çünkü yalnızca mainDb servisini dikkate alır.
Autowiring, di › excluded
yapılandırma seçeneğiyle tüm türler için genel olarak da kapatılabilir; bu seçenek, hiçbir zaman autowiring'e girmemesi
gereken türleri (ve torunlarını) listeler.
Nette'deki autowiring yapılandırması Symfony'dekinden farklıdır. Symfony'de autowire: false,
autowiring'in servisin yapıcı argümanları için kullanılmaması gerektiği anlamına gelir. Nette'de autowiring, yapıcı
argümanları ile container aracılığıyla çağrılan diğer metotlar (setter injection gibi) için geçerlidir.
autowired: false seçeneği, container'ın bu servis örneğini başka servislere bağımlılık olarak otomatik
aktarmasını engeller.
Autowiring Yeğlemesi
Aynı türde birden çok servisimiz varsa ve bunlardan biri için autowired seçeneğini belirtirsek, o servis
yeğlenen servis olur:
services:
mainDb:
create: PDO(%dsn%, %user%, %password%)
autowired: PDO # yeğlenen olur
tempDb:
create: PDO('sqlite::memory:')
articles: Model\ArticleRepository
articles servisi, birden çok PDO servisinin (mainDb ve tempDb) uyduğuna
dair istisna fırlatmaz; yeğlenen servisi, yani mainDb servisini kullanır.
Servis Koleksiyonu
Autowiring, belirli bir türdeki servislerin dizilerini de aktarabilir. PHP tür bildirimlerinde dizi öğelerinin türünü
belirtmeyi doğrudan desteklemediğinden, array tür bildirimini öğe türünü belirten ClassName[]
gibi bir phpDoc yorumuyla tamamlamalısınız:
namespace Model;
class ShipManager
{
/**
* @param Shipper[] $shippers
*/
public function __construct(array $shippers)
{}
}
DI container o zaman verilen türe karşılık gelen servislerden oluşan bir diziyi otomatik olarak aktarır. Autowiring'i kapatılmış servisleri dışarıda bırakır ve o an oluşturulmakta olan servisi asla kendi koleksiyonuna katmaz. Tek bir servis aktarmanın aksine, autowiring'i belirli bir türe daraltmanın ya da bir servisi yeğlenen işaretlemenin burada etkisi yoktur; dizi her zaman verilen türdeki tüm servisleri içerir.
Yorumdaki tür array<int, Class> ya da list<Class> biçiminde de olabilir. phpDoc
yorumunun biçimini denetleyemiyorsanız, servis dizisini yapılandırmada doğrudan typed() ile
aktarabilirsiniz.
Skaler Argümanlar
Autowiring yalnızca nesnelerde ve nesne dizilerinde çalışır. Skaler argümanlar (örneğin dizeler, sayılar, boolean değerler) yapılandırmada belirtilmelidir. Bir alternatif, skaler değeri (ya da birden çok değeri) içine alan bir ayar nesnesi oluşturmaktır. Bu nesne sonra autowiring ile aktarılabilir.
class MySettings
{
public function __construct(
// readonly, PHP 8.1'den beri kullanılabilir
public readonly bool $value,
)
{}
}
Onu yapılandırmaya ekleyerek servis olarak kaydedersiniz:
services:
- MySettings('any value')
Diğer sınıflar sonra onu autowiring ile isteyebilir.
İsteğe Bağlı Bağımlılıklar
Bir yapıcının ya da metodun parametresinin varsayılan değeri varsa ve container'da gereken türde bir servis yoksa, autowiring istisna fırlatmaz; argümanı yalnızca atlar, böylece varsayılan değer kullanılır. İsteğe bağlı bağımlılıkları böyle bildirirsiniz:
class Foo
{
public function __construct(
private ?Logger $logger = null,
) {}
}
Buna karşılık varsayılan değeri olmayan bir parametrede, eksik servis her zaman istisnaya yol açar.
Autowiring'i Daraltma
Tek tek servislerde autowiring, belirli sınıflara ya da arayüzlere daraltılabilir.
Autowiring normalde bir servisi, türü servisle uyuşan her metot parametresine aktarır. Daraltma, servisin aktarılabilmesi için metot parametrelerinde belirtilen türlerin karşılaması gereken koşulları koymak demektir.
Bir örnek alalım:
class ParentClass
{}
class ChildClass extends ParentClass
{}
class ParentDependent
{
function __construct(ParentClass $obj)
{}
}
class ChildDependent
{
function __construct(ChildClass $obj)
{}
}
Hepsini servis olarak kaydetseydik autowiring başarısız olurdu:
services:
parent: ParentClass
child: ChildClass
parentDep: ParentDependent # İSTİSNA FIRLATIR, hem parent hem child uyuyor
childDep: ChildDependent # autowiring yapıcıya child servisini aktarır
parentDep servisi Multiple services of type ParentClass found: child, parent istisnasını
fırlatır; çünkü hem parent hem de child servisleri onun yapıcısına uyar ve autowiring hangisini
seçeceğine karar veremez.
Bu yüzden child servisi için autowiring'i ChildClass türüne daraltabiliriz:
services:
parent: ParentClass
child:
create: ChildClass
autowired: ChildClass # 'autowired: self' olarak da yazılabilir
parentDep: ParentDependent # autowiring yapıcıya parent servisini aktarır
childDep: ChildDependent # autowiring yapıcıya child servisini aktarır
Artık parentDep servisinin yapıcısına parent servisi aktarılır; çünkü uyan tek nesne odur.
child servisi artık autowiring ile oraya aktarılmaz. Evet, child servisi hâlâ
ParentClass türündedir, ama autowired: ChildClass daraltma koşulu, yalnızca açıkça
ChildClass (ya da alt türleri) olarak türlenen parametrelere aktarılacağı anlamına gelir.
ParentDependent sınıfı ParentClass istediğinden, child servisi orada artık autowiring
adayı sayılmaz.
child servisi için autowired: ChildClass yerine autowired: self de yazılabilirdi;
çünkü self, geçerli servisin sınıfı için bir yer tutucudur.
autowired anahtarında dizi olarak birden çok sınıf ya da arayüz belirtmek de olanaklıdır:
autowired: [ParentClass, FooInterface]
Örneğe arayüzler eklemeyi 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)
{}
}
child servisini hiçbir şekilde kısıtlamazsak, FooDependent, BarDependent,
ParentDependent ve ChildDependent sınıflarının tüm yapıcılarına uyar ve autowiring onu oralara
aktarır.
Ancak autowiring'ini autowired: ChildClass (ya da self) ile ChildClass türüne
daraltırsak, autowiring onu yalnızca ChildDependent yapıcısına aktarır; çünkü o ChildClass
türünde bir argüman ister ve ChildClass, ChildClass türündendir. Diğer parametrelerin
istediği türlerin hiçbiri ChildClass ya da onun alt türü değildir, dolayısıyla servis onlara aktarılmaz.
Onu autowired: ParentClass ile ParentClass türüne kısıtlarsak, autowiring onu yine
ChildDependent yapıcısına (çünkü istenen ChildClass, ParentClass türünün alt
türüdür) ve artık ParentDependent yapıcısına da aktarır; çünkü istenen ParentClass türü de
uygundur.
Onu FooInterface türüne kısıtlarsak, ParentDependent (istenen ParentClass,
FooInterface türünün alt türüdür) ve ChildDependent sınıflarına yine autowiring ile
aktarılır; buna ek olarak FooDependent yapıcısına da aktarılır, ama BarDependent sınıfına
aktarılmaz; çünkü BarInterface, FooInterface türünün alt türü değildir.
services:
child:
create: ChildClass
autowired: FooInterface
fooDep: FooDependent # autowiring yapıcıya child servisini aktarır
barDep: BarDependent # İSTİSNA FIRLATIR, uyan servis yok
parentDep: ParentDependent # autowiring yapıcıya child servisini aktarır
childDep: ChildDependent # autowiring yapıcıya child servisini aktarır