Üretilmiş Fabrikalar

Nette DI, arayüzlere dayalı olarak fabrika kodunu otomatik olarak üretebilir, bu da size kod yazmaktan tasarruf sağlar.

Fabrika, nesneleri üreten ve yapılandıran bir sınıftır. Dolayısıyla onlara bağımlılıklarını da aktarır. Lütfen bunu, fabrikaların belirli bir kullanım şeklini açıklayan ve bu konuyla ilgisi olmayan factory method tasarım deseniyle karıştırmayın.

Böyle bir fabrikanın nasıl göründüğünü giriş bölümü içinde gösterdik:

class ArticleFactory
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}

	public function create(): Article
	{
		return new Article($this->db);
	}
}

Nette DI, fabrika kodunu otomatik olarak üretebilir. Tek yapmanız gereken bir arayüz oluşturmaktır ve Nette DI uygulamayı üretecektir. Arayüzün tam olarak create adında bir metodu olmalı ve dönüş değeri tipini bildirmelidir:

interface ArticleFactory
{
	function create(): Article;
}

Yani ArticleFactory fabrikasının, Article nesneleri oluşturan bir create metodu vardır. Article sınıfı örneğin şöyle görünebilir:

class Article
{
	public function __construct(
		private Nette\Database\Connection $db,
	) {
	}
}

Fabrikayı yapılandırma dosyasına ekleriz:

services:
	- ArticleFactory

Nette DI, fabrikanın ilgili uygulamasını üretecektir.

Fabrikayı kullanan kodda, nesneyi arayüze göre talep ederiz ve Nette DI üretilen uygulamayı kullanır:

class UserController
{
	public function __construct(
		private ArticleFactory $articleFactory,
	) {
	}

	public function foo()
	{
		// fabrikanın nesneyi oluşturmasına izin veririz
		$article = $this->articleFactory->create();
	}
}

Parametreli Fabrika

Fabrika metodu create, daha sonra kurucuya aktarılacak parametreleri kabul edebilir. Örneğin, Article sınıfına makale yazarının ID'sini ekleyelim:

class Article
{
	public function __construct(
		private Nette\Database\Connection $db,
		private int $authorId,
	) {
	}
}

Parametreyi fabrikaya da ekleriz:

interface ArticleFactory
{
	function create(int $authorId): Article;
}

Kurucudaki parametre ile fabrikadaki parametrenin aynı adı taşıması sayesinde, Nette DI bunları tamamen otomatik olarak aktarır.

Gelişmiş Tanım

Tanım, implement anahtarını kullanarak çok satırlı bir formda da yazılabilir:

services:
	articleFactory:
		implement: ArticleFactory

Bu daha uzun yolla yazarken, normal servislerde olduğu gibi arguments anahtarında kurucu için ek argümanlar ve setup kullanarak ek yapılandırma belirtmek mümkündür.

Örnek: Eğer create() metodu $authorId parametresini kabul etmeseydi, yapılandırmada Article kurucusuna aktarılacak sabit bir değer belirtebilirdik:

services:
	articleFactory:
		implement: ArticleFactory
		arguments:
			authorId: 123

Veya tam tersi, eğer create() $authorId parametresini kabul etseydi, ancak kurucunun bir parçası olmasaydı ve Article::setAuthorId() metoduyla aktarılsaydı, ona setup bölümünde başvururduk:

services:
	articleFactory:
		implement: ArticleFactory
		setup:
			- setAuthorId($authorId)

Erişimci (Accessor)

Nette, fabrikaların yanı sıra erişimciler (accessor) olarak adlandırılanları da üretebilir. Bunlar, DI konteynerinden belirli bir servisi döndüren bir get() metoduna sahip nesnelerdir. get()'in tekrarlanan çağrıları her zaman aynı örneği döndürür.

Erişimciler, bağımlılıklara tembel yükleme (lazy-loading) sağlar. Hataları özel bir veritabanına yazan bir sınıfımız olduğunu varsayalım. Eğer bu sınıf veritabanı bağlantısını kurucu bağımlılığı olarak alsaydı, bağlantı her zaman oluşturulmak zorunda kalırdı, ancak pratikte hata yalnızca istisnai olarak ortaya çıkar ve bu nedenle çoğu zaman bağlantı kullanılmadan kalırdı. Bunun yerine, sınıf bir erişimci aktarır ve yalnızca onun get() metodu çağrıldığında veritabanı nesnesi oluşturulur:

Bir erişimci nasıl oluşturulur? Sadece bir arayüz yazmanız yeterlidir ve Nette DI uygulamayı üretecektir. Arayüzün tam olarak get adında bir metodu olmalı ve dönüş değeri tipini bildirmelidir:

interface PDOAccessor
{
	function get(): PDO;
}

Erişimciyi, döndüreceği servisin tanımının da bulunduğu yapılandırma dosyasına ekleriz:

services:
	- PDOAccessor
	- PDO(%dsn%, %user%, %password%)

Erişimci PDO tipinde bir servis döndürdüğü ve yapılandırmada bu türden tek bir servis olduğu için, tam olarak onu döndürecektir. Eğer ilgili tipten birden fazla servis olsaydı, döndürülen servisi adıyla belirlerdik, örn. - PDOAccessor(@db1).

Çoklu Fabrika/Erişimci

Fabrikalarımız ve erişimcilerimiz şimdiye kadar her zaman yalnızca bir nesne üretebildi veya döndürebildi. Ancak, erişimcilerle birleştirilmiş çoklu fabrikaları da çok kolay bir şekilde oluşturmak mümkündür. Böyle bir sınıfın arayüzü, create<name>() ve get<name>() adlarına sahip istenilen sayıda metot içerecektir, örn.:

interface MultiFactory
{
	function createArticle(): Article;
	function getDb(): PDO;
}

Yani, birkaç üretilmiş fabrika ve erişimci aktarmak yerine, daha fazlasını yapabilen daha karmaşık bir fabrika aktarırız.

Alternatif olarak, birkaç metot yerine parametreli get() kullanılabilir:

interface MultiFactoryAlt
{
	function get($name): PDO;
}

O zaman MultiFactory::getArticle()'ın MultiFactoryAlt::get('article') ile aynı şeyi yaptığı geçerlidir. Ancak, alternatif yazımın dezavantajı, hangi $name değerlerinin desteklendiğinin açık olmaması ve mantıksal olarak arayüzde farklı $name için farklı dönüş değerlerini ayırt etmenin mümkün olmamasıdır.

Liste ile Tanım

Bu şekilde, yapılandırmada çoklu bir fabrika tanımlanabilir:

services:
	- MultiFactory(
		article: Article                      # createArticle() tanımlar
		db: PDO(%dsn%, %user%, %password%)    # getDb() tanımlar
	)

Veya fabrika tanımında mevcut servislere referansla başvurabiliriz:

services:
	article: Article
	- PDO(%dsn%, %user%, %password%)
	- MultiFactory(
		article: @article    # createArticle() tanımlar
		db: @\PDO            # getDb() tanımlar
	)

Etiketlerle Tanım

İkinci seçenek, tanım için etiketleri kullanmaktır:

services:
	- App\Core\RouterFactory::createRouter
	- App\Model\DatabaseAccessor(
		db1: @database.db1.explorer
	)
versiyon: 3.x