Mi az a DI konténer?

A Dependency injection konténer (DIC) egy olyan osztály, amely képes objektumokat példányosítani és konfigurálni.

Talán meglepő, de sok esetben nincs szüksége dependency injection konténerre ahhoz, hogy kihasználja a dependency injection (röviden DI) előnyeit. Hiszen már a bevezető fejezetben is konkrét példákon keresztül mutattuk be a DI-t, és nem volt szükség semmilyen konténerre.

Ha azonban nagyszámú, sok függőséggel rendelkező különböző objektumot kell kezelnie, a dependency injection konténer valóban hasznos lesz. Ez például a keretrendszerre épülő webalkalmazások esetében igaz.

Az előző fejezetben bemutattuk az Article és UserController osztályokat. Mindkettőnek vannak bizonyos függőségei, nevezetesen az adatbázis és az ArticleFactory factory. És ezekhez az osztályokhoz most létrehozunk egy konténert. Természetesen egy ilyen egyszerű példához nincs értelme konténert használni. De létrehozzuk, hogy megmutassuk, hogyan néz ki és működik.

Itt van egy egyszerű hardcoded konténer a megadott példához:

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());
	}
}

A használat így nézne ki:

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

Csak megkérdezzük a konténert az objektumról, és már nem kell tudnunk semmit arról, hogyan kell létrehozni, és milyen függőségei vannak; mindezt a konténer tudja. A függőségeket a konténer automatikusan injektálja. Ebben rejlik az ereje.

A konténernek eddig minden adata fixen be van írva. Tegyünk tehát egy újabb lépést, és adjunk hozzá paramétereket, hogy a konténer valóban hasznos legyen:

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' => '***',
]);

Az éles szemű olvasók talán észrevettek egy problémát. Minden alkalommal, amikor lekérünk egy UserController objektumot, új ArticleFactory példány és adatbázis is létrejön. Ezt biztosan nem akarjuk.

Ezért hozzáadunk egy getService() metódust, amely mindig ugyanazokat a példányokat adja vissza:

class Container
{
	private array $services = [];

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

	public function getService(string $name): object
	{
		if (!isset($this->services[$name])) {
			// a getService('Database') a createDatabase()-t fogja hívni
			$method = 'create' . $name;
			$this->services[$name] = $this->$method();
		}
		return $this->services[$name];
	}

	// ...
}

Az első híváskor, pl. $container->getService('Database'), a createDatabase() metódussal létrehozza az adatbázis objektumot, amelyet a $services tömbbe ment, és a következő híváskor egyenesen visszaadja.

Módosítjuk a konténer többi részét is, hogy a getService()-t használja:

class Container
{
	// ...

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

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

Mellesleg, a szolgáltatás kifejezés bármely, a konténer által kezelt objektumot jelöl. Ezért is a metódus neve getService().

Kész. Van egy teljesen működőképes DI konténerünk! És használhatjuk:

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

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

Ahogy láthatja, egy DIC megírása nem bonyolult dolog. Érdemes megjegyezni, hogy maguk az objektumok nem tudják, hogy valamilyen konténer hozza őket létre. Így bármilyen PHP objektumot létre lehet hozni anélkül, hogy a forráskódjába bele kellene nyúlni.

A konténer osztály manuális létrehozása és karbantartása meglehetősen gyorsan rémálommá válhat. Ezért a következő fejezetben a Nette DI Container-ről beszélünk, amely szinte önmagát tudja generálni és frissíteni.

verzió: 3.x