Kaj je DI vsebnik?

Dependency injection vsebnik (DIC) je razred, ki zna instancirati in konfigurirati objekte.

Morda vas bo presenetilo, toda v mnogih primerih ne potrebujete dependency injection vsebnika, da bi lahko izkoristili prednosti dependency injection (kratko DI). Saj smo si tudi v uvodnem poglavju na konkretnih primerih DI pokazali in noben vsebnik ni bil potreben.

Če pa morate upravljati veliko število različnih objektov z mnogimi odvisnostmi, bo dependency injection vsebnik resnično koristen. Kar je na primer primer spletnih aplikacij, zgrajenih na ogrodju.

V prejšnjem poglavju smo si predstavili razreda Article in UserController. Oba imata neke odvisnosti, in sicer podatkovno bazo in tovarno ArticleFactory. In za te razrede si zdaj ustvarimo vsebnik. Seveda za tako preprost primer nima smisla imeti vsebnika. Ampak ga bomo ustvarili, da si pokažemo, kako izgleda in deluje.

Tukaj je preprost hardcoded vsebnik za navedeni primer:

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

Uporaba bi izgledala takole:

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

Vsebniku samo vprašamo za objekt in že nam ni treba vedeti ničesar o tem, kako ga ustvariti in kakšne ima odvisnosti; vse to ve vsebnik. Odvisnosti so z vsebnikom injicirane samodejno. V tem je njegova moč.

Vsebnik ima zaenkrat zapisane vse podatke trdo kodirano. Naredimo torej naslednji korak in dodajmo parametre, da bo vsebnik resnično koristen:

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

Bistri bralci so morda opazili določeno težavo. Vsakič, ko pridobim objekt UserController, se ustvari tudi nova instanca ArticleFactory in podatkovne baze. Tega zagotovo nočemo.

Dodajmo zato metodo getService(), ki bo vračala vedno iste instance:

class Container
{
	private array $services = [];

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

	public function getService(string $name): object
	{
		if (!isset($this->services[$name])) {
			// getService('Database') bo klical createDatabase()
			$method = 'create' . $name;
			$this->services[$name] = $this->$method();
		}
		return $this->services[$name];
	}

	// ...
}

Pri prvem klicu npr. $container->getService('Database') si pusti od createDatabase() ustvariti objekt podatkovne baze, ki ga shrani v polje $services in pri naslednjem klicu ga takoj vrne.

Prilagodimo tudi preostanek vsebnika, da bo uporabljal getService():

class Container
{
	// ...

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

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

Mimogrede, izraz storitev se nanaša na kateri koli objekt, ki ga upravlja vsebnik. Zato tudi ime metode getService().

Končano. Imamo popolnoma funkcionalen DI vsebnik! In lahko ga uporabimo:

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

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

Kot vidite, napisati DIC ni nič zapletenega. Omeniti velja, da sami objekti ne vedo, da jih ustvarja nek vsebnik. S tem je mogoče tako ustvarjati kateri koli objekt v PHP brez posega v njegovo izvorno kodo.

Ročno ustvarjanje in vzdrževanje razreda vsebnika se lahko precej hitro spremeni v nočno moro. V naslednjem poglavju si zato povemo o Nette DI Containeru, ki se zna generirati in posodabljati skoraj sam.

različica: 3.x