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