Ce este un container DI?

Containerul de injecție de dependență (DIC) este o clasă care poate instanția și configura obiecte.

Poate vă va surprinde, dar în multe cazuri nu aveți nevoie de un container de injecție de dependență pentru a beneficia de avantajele injecției de dependență (pe scurt DI). Până la urmă, chiar și în capitolul introductiv am arătat DI pe exemple concrete și nu a fost nevoie de niciun container.

Cu toate acestea, dacă trebuie să gestionați un număr mare de obiecte diferite cu multe dependențe, un container de injecție de dependență va fi cu adevărat util. Ceea ce este cazul aplicațiilor web construite pe un framework.

În capitolul anterior, am prezentat clasele Article și UserController. Ambele au anumite dependențe, și anume baza de date și factory-ul ArticleFactory. Și pentru aceste clase vom crea acum un container. Desigur, pentru un exemplu atât de simplu nu are sens să avem un container. Dar îl vom crea pentru a arăta cum arată și cum funcționează.

Iată un container simplu hardcodat pentru exemplul dat:

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

Utilizarea ar arăta astfel:

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

Întrebăm doar containerul despre obiect și nu mai trebuie să știm nimic despre cum să îl creăm și ce dependențe are; containerul știe toate acestea. Dependențele sunt injectate automat de container. Aici stă puterea sa.

Containerul are deocamdată toate datele scrise hardcodat. Vom face deci următorul pas și vom adăuga parametri pentru ca containerul să fie cu adevărat util:

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

Cititorii atenți ar fi putut observa o anumită problemă. De fiecare dată când obțin obiectul UserController, se creează și o nouă instanță ArticleFactory și a bazei de date. Cu siguranță nu dorim acest lucru.

Vom adăuga deci metoda getService(), care va returna mereu aceleași instanțe:

class Container
{
	private array $services = [];

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

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

	// ...
}

La primul apel, de ex. $container->getService('Database'), va lăsa createDatabase() să creeze obiectul bazei de date, pe care îl va stoca în array-ul $services și la următorul apel îl va returna direct.

Modificăm și restul containerului pentru a utiliza getService():

class Container
{
	// ...

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

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

Apropo, termenul serviciu se referă la orice obiect gestionat de container. De aceea și numele metodei getService().

Gata. Avem un container DI complet funcțional! Și îl putem folosi:

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

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

După cum puteți vedea, scrierea unui DIC nu este nimic complicat. Merită menționat că obiectele în sine nu știu că sunt create de vreun container. Astfel, este posibil să se creeze în acest mod orice obiect în PHP fără a interveni în codul său sursă.

Crearea și întreținerea manuală a clasei containerului poate deveni destul de repede un coșmar. De aceea, în capitolul următor vom vorbi despre Containerul Nette DI, care se poate genera și actualiza aproape singur.

versiune: 3.x