DI Konteyner Nedir?

Dependency injection container (DIC), nesneleri örnekleyebilen ve yapılandırabilen bir sınıftır.

Sizi şaşırtabilir, ancak çoğu durumda bağımlılık enjeksiyonundan (kısaca DI) yararlanmak için bir bağımlılık enjeksiyon konteynerine ihtiyacınız yoktur. Sonuçta, önceki bölümde bile DI'nin belirli örneklerini gösterdik ve hiçbir kapsayıcıya ihtiyaç duyulmadı.

Bununla birlikte, çok sayıda bağımlılığa sahip çok sayıda farklı nesneyi yönetmeniz gerekiyorsa, bir bağımlılık enjeksiyon konteyneri gerçekten yararlı olacaktır. Bu belki de bir çerçeve üzerine inşa edilmiş web uygulamaları için geçerlidir.

Bir önceki bölümde Article ve UserController sınıflarını tanıtmıştık. Her ikisinin de bazı bağımlılıkları vardır, yani veritabanı ve fabrika ArticleFactory. Ve bu sınıflar için şimdi bir konteyner oluşturacağız. Elbette, bu kadar basit bir örnek için bir konteynere sahip olmak mantıklı değildir. Ancak nasıl göründüğünü ve çalıştığını göstermek için bir tane oluşturacağız.

İşte yukarıdaki örnek için basit bir kodlanmış kapsayıcı:

class Container
{
	public function createDatabase(): Nette\Database\Connection
	{
		return new Nette\Database\Connection('mysql:', 'root', '***');
	}

	public function createArticleFactory(): ArticleFactory
	{
		return new ArticleFactory($this->createDatabase());
	}

	public function createUserController(): UserController
	{
		return new UserController($this->createArticleFactory());
	}
}

Kullanım şu şekilde olacaktır:

$container = new Container;
$controller = $container->createUserController();

Konteynerden sadece nesneyi isteriz ve artık nesnenin nasıl oluşturulacağı veya bağımlılıklarının ne olduğu hakkında hiçbir şey bilmemize gerek yoktur; konteyner bunların hepsini bilir. Bağımlılıklar konteyner tarafından otomatik olarak enjekte edilir. Bu onun gücüdür.

Şimdiye kadar konteynerde her şey kodlanmıştı. Bu yüzden bir sonraki adımı atıyoruz ve konteyneri gerçekten kullanışlı hale getirmek için parametreler ekliyoruz:

class Container
{
	public function __construct(
		private array $parameters,
	) {
	}

	public function createDatabase(): Nette\Database\Connection
	{
		return new Nette\Database\Connection(
			$this->parameters['db.dsn'],
			$this->parameters['db.user'],
			$this->parameters['db.password'],
		);
	}

	// ...
}

$container = new Container([
	'db.dsn' => 'mysql:',
	'db.user' => 'root',
	'db.password' => '***',
]);

Dikkatli okuyucular bir sorun fark etmiş olabilirler. Ne zaman bir nesne alsam UserController, yeni bir örnek ArticleFactory ve veritabanı da oluşturuluyor. Bunu kesinlikle istemiyoruz.

Bu yüzden aynı örnekleri tekrar tekrar döndürecek bir getService() yöntemi ekliyoruz:

class Container
{
	private array $services = [];

	public function __construct(
		private array $parameters,
	) {
	}

	public function getService(string $name): object
	{
		if (!isset($this->services[$name])) {
			// getService('Database') createDatabase() işlevini çağırır
			$method = 'create' . $name;
			$this->services[$name] = $this->$method();
		}
		return $this->services[$name];
	}

	// ...
}

Örneğin $container->getService('Database') adresine yapılan ilk çağrıda createDatabase() bir veritabanı nesnesi oluşturacak, bu nesneyi $services dizisinde saklayacak ve bir sonraki çağrıda doğrudan geri döndürecektir.

Ayrıca konteynerin geri kalanını getService() kullanacak şekilde değiştiriyoruz:

class Container
{
	// ...

	public function createArticleFactory(): ArticleFactory
	{
		return new ArticleFactory($this->getService('Database'));
	}

	public function createUserController(): UserController
	{
		return new UserController($this->getService('ArticleFactory'));
	}
}

Bu arada, hizmet terimi konteyner tarafından yönetilen herhangi bir nesneyi ifade eder. Bu nedenle yöntem adı getService().

Tamamdır. Tamamen işlevsel bir DI konteynerimiz var! Ve onu kullanabiliriz:

$container = new Container([
	'db.dsn' => 'mysql:',
	'db.user' => 'root',
	'db.password' => '***',
]);

$controller = $container->getService('UserController');
$database = $container->getService('Database');

Gördüğünüz gibi, bir DIC yazmak zor değil. Nesnelerin kendilerinin bir kapsayıcının onları yarattığını bilmemeleri dikkat çekicidir. Böylece, PHP'de herhangi bir nesneyi kaynak kodunu etkilemeden bu şekilde oluşturmak mümkündür.

Bir konteyner sınıfını manuel olarak oluşturmak ve sürdürmek oldukça hızlı bir şekilde kabusa dönüşebilir. Bu nedenle, bir sonraki bölümde kendini neredeyse otomatik olarak oluşturabilen ve güncelleyebilen Nette DI Container hakkında konuşacağız.

versiyon: 3.x