DI Container Nedir?
Bağımlılık enjeksiyonu container'ı (DIC ya da DI container), başka nesneleri (servis denir) örneklemekten ve yapılandırmaktan sorumlu bir nesnedir.
Sizi şaşırtabilir, ama çoğu durumda bağımlılık enjeksiyonunun (kısaca DI) yararlarından yararlanmak için bir bağımlılık enjeksiyonu container'ına ihtiyacınız yoktur. Nitekim giriş bölümünde somut DI örnekleri gösterdik ve hiçbirinde container gerekmedi.
Ancak karmaşık bağımlılıkları olan çok sayıda nesneyi yönetirken DI container çok yararlı hâle gelir. Bir framework üzerine kurulmuş web uygulamalarında durum çoğu zaman böyledir.
Önceki bölümde Article ve EditController sınıflarını tanıttık. İkisinin de
bağımlılıkları var: veritabanı ve ArticleFactory factory'si. Şimdi bu sınıflar için bir container
oluşturacağız. Elbette bu kadar basit bir örnek için container oluşturmak fazlasıyla abartılı. Ama nasıl
göründüğünü ve nasıl çalıştığını göstermek için oluşturacağız.
İşte yukarıdaki örnek için basit, sabit kodlanmış bir container:
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 createEditController(): EditController
{
return new EditController($this->createArticleFactory());
}
}
Kullanımı şöyle görünürdü:
$container = new Container;
$controller = $container->createEditController();
Nesneyi container'dan yalnızca isteriz; onu nasıl oluşturacağımızı ya da bağımlılıklarının neler olduğunu bilmemize gerek yoktur, bunların hepsini container üstlenir. Bağımlılıklar container tarafından otomatik olarak enjekte edilir. Gücü buradan gelir.
Şu an container'ın tüm bilgileri sabit kodlanmış durumda. Öyleyse bir sonraki adımı atalım ve container'ı gerçekten yararlı kılmak için parametre ekleyelim:
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' => '***',
]);
Keskin gözlü okurlar bir sorun fark edebilir. Her EditController nesnesi aldığımızda,
ArticleFactory ile veritabanı bağlantısından da yeni örnekler oluşuyor. Bunu kesinlikle istemiyoruz.
Bu yüzden, her zaman aynı örnekleri döndürecek bir getService() metodu ekleyeceğiz:
class Container
{
private array $services = [];
public function __construct(
private array $parameters,
) {
}
public function getService(string $name): object
{
if (!isset($this->services[$name])) {
// getService('Database') çağrısı createDatabase() metodunu çağırır
$method = 'create' . $name;
$this->services[$name] = $this->$method();
}
return $this->services[$name];
}
// ...
}
İlk çağrıda, örneğin $container->getService('Database') çağrısında, veritabanı nesnesini oluşturmak
için createDatabase() metodunu çağırır, onu $services dizisinde saklar ve döndürür. Sonraki
çağrılarda ise doğrudan saklanan örneği döndürür.
Container'ın geri kalanını da getService() kullanacak şekilde değiştiriyoruz:
class Container
{
// ...
public function createArticleFactory(): ArticleFactory
{
return new ArticleFactory($this->getService('Database'));
}
public function createEditController(): EditController
{
return new EditController($this->getService('ArticleFactory'));
}
}
Bu arada, servis terimi container'ın yönettiği herhangi bir nesneyi anlatır. Metodun adı da bu yüzden
getService().
Bitti. Tam işlevsel bir DI container'ımız var! Ve onu kullanabiliriz:
$container = new Container([
'db.dsn' => 'mysql:',
'db.user' => 'root',
'db.password' => '***',
]);
$controller = $container->getService('EditController');
$database = $container->getService('Database');
Gördüğünüz gibi bir DIC yazmak zor değil. Nesnelerin kendilerinin, onları bir container'ın oluşturduğundan habersiz olduğunu belirtmekte yarar var. Dolayısıyla herhangi bir PHP nesnesi, kaynak kodunu değiştirmeden bu şekilde oluşturulabilir.
Container sınıfını elle oluşturup bakımını yapmak hızla bir kâbusa dönüşebilir. Bu yüzden bir sonraki bölümde, neredeyse tümüyle otomatik olarak kendini üretip güncelleyebilen Nette DI Container konusunu ele alacağız.