Ce este DI Container?
Containerul de injecție a dependențelor (DIC) este o clasă care poate instanția și configura obiecte.
S-ar putea să vă surprindă, dar în multe cazuri nu aveți nevoie de un container de injecție a dependențelor pentru a profita de injecția dependențelor (abreviat DI). La urma urmei, chiar și în capitolul anterior am arătat exemple specifice de DI ș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 a dependențelor va fi cu adevărat util. Ceea ce este probabil cazul aplicațiilor web construite pe un framework.
În capitolul anterior, am introdus clasele Article
și UserController
. Ambele au unele dependențe,
și anume baza de date și fabrica 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 vom crea unul pentru a arăta cum arată și cum
funcționează.
Iată un container simplu, codat pentru exemplul de mai sus:
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();
Pur și simplu cerem containerului obiectul și nu mai trebuie să știm nimic despre cum să îl creăm sau care sunt dependențele sale; containerul știe toate acestea. Dependențele sunt injectate automat de către container. Aceasta este puterea sa.
Până în prezent, containerul are totul codificat. Așadar, facem următorul pas și adăugăm parametri pentru a face containerul 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 isteți ar fi putut observa o problemă. De fiecare dată când obțin un obiect UserController
, se
creează, de asemenea, o nouă instanță ArticleFactory
și o bază de date. Cu siguranță nu ne dorim
acest lucru.
Așa că adăugăm o metodă getService()
care va returna aceleași instanțe la nesfârșit:
class Container
{
private array $services = [];
public function __construct(
private array $parameters,
) {
}
public function getService(string $name): object
{
if (!isset($this->services[$name])) {
// getService('Database') apelează createDatabase()
$method = 'create' . $name;
$this->services[$name] = $this->$method();
}
return $this->services[$name];
}
// ...
}
La primul apel la, de exemplu, $container->getService('Database')
, createDatabase()
va crea un
obiect de bază de date, pe care îl va stoca în matricea $services
și îl va returna direct la
următorul apel.
De asemenea, modificăm 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 de serviciu se referă la orice obiect gestionat de container. De aici și numele metodei
getService()
.
S-a făcut. 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, nu este dificil să scrieți un DIC. Este de remarcat faptul că obiectele însele nu știu că sunt create de un container. Astfel, este posibil să se creeze orice obiect în PHP în acest mod, fără a afecta codul sursă al acestora.
Crearea și întreținerea manuală a unei clase container poate deveni un coșmar destul de repede. Prin urmare, în capitolul următor vom vorbi despre Nette DI Container, care se poate genera și actualiza aproape automat.