Mi az a DI konténer?

A függőségi injektálási konténer (DIC) egy olyan osztály, amely képes objektumok példányosítására és konfigurálására.

Meglepő lehet, de sok esetben nincs szükség függőségi injektáló konténerre ahhoz, hogy kihasználjuk a függőségi injektálás (röviden DI) előnyeit. Végül is, még az előző fejezetben is mutattunk konkrét példákat a DI-re, és nem volt szükség konténerre.

Ha azonban nagyszámú különböző objektumot kell kezelnünk sok függőséggel, akkor egy függőségi injektálási konténer igazán hasznos lesz. Ami talán a keretrendszerre épülő webes alkalmazások esetében áll fenn.

Az előző fejezetben bemutattuk a Article és a UserController osztályokat. Mindkettőnek van néhány függősége, nevezetesen az adatbázis és a gyár ArticleFactory. Ezekhez az osztályokhoz pedig most létrehozunk egy konténert. Természetesen egy ilyen egyszerű példánál nincs értelme konténernek. De létrehozunk egyet, hogy megmutassuk, hogyan néz ki és hogyan működik.

Íme egy egyszerű, keményen kódolt konténer a fenti 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();

Nem kell többé tudnunk semmit arról, hogyan hozzuk létre, vagy mik a függőségei; a konténer mindezt tudja. A függőségeket a konténer automatikusan injektálja. Ez az ereje.

Eddig a konténer mindent keményen kódolt. Ezért megtesszük a következő lépést, és paramétereket adunk hozzá, 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 eszű olvasók talán észrevettek egy problémát. Minden alkalommal, amikor kapok egy objektumot UserController, egy új példányt ArticleFactory és az adatbázis is létrejön. Ezt semmiképpen sem akarjuk.

Ezért hozzáadunk egy getService() metódust, amely újra és újra 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])) {
			// getService('Database') hívja a createDatabase()
			$method = 'create' . $name;
			$this->services[$name] = $this->$method();
		}
		return $this->services[$name];
	}

	// ...
}

A $container->getService('Database') első hívására a createDatabase() létrehoz egy adatbázis-objektumot, amelyet a $services tömbben tárol, és a következő híváskor közvetlenül visszaadja.

A konténer többi részét is úgy módosítjuk, 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'));
	}
}

Egyébként a szolgáltatás kifejezés a konténer által kezelt bármely objektumra vonatkozik. Ezért a metódus neve getService().

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

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

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

Amint láthatjuk, nem nehéz DIC-et írni. Figyelemre méltó, hogy maguk az objektumok nem tudják, hogy egy konténer hozza létre őket. Így bármilyen objektumot létrehozhatunk így a PHP-ben anélkül, hogy a forráskódjukat befolyásolnánk.

Egy konténer osztály manuális létrehozása és karbantartása elég gyorsan rémálommá válhat. Ezért a következő fejezetben a Nette DI Containerről lesz szó, amely szinte automatikusan képes létrehozni és frissíteni magát.

verzió: 3.x