Τι είναι το DI Container;

Το Dependency injection container (DIC) είναι μια κλάση που μπορεί να ενσαρκώνει και να διαμορφώνει αντικείμενα.

Μπορεί να σας εκπλήξει, αλλά σε πολλές περιπτώσεις δεν χρειάζεστε ένα dependency injection container για να εκμεταλλευτείτε το dependency injection (DI για συντομία). Εξάλλου, ακόμη και σε προηγούμενο κεφάλαιο δείξαμε συγκεκριμένα παραδείγματα DI και δεν χρειαζόταν κανένας περιέκτης.

Ωστόσο, αν πρέπει να διαχειριστείτε έναν μεγάλο αριθμό διαφορετικών αντικειμένων με πολλές εξαρτήσεις, ένας περιέκτης dependency injection θα είναι πραγματικά χρήσιμος. Κάτι που ίσως ισχύει για εφαρμογές ιστού που είναι χτισμένες σε ένα πλαίσιο.

Στο προηγούμενο κεφάλαιο, παρουσιάσαμε τις κλάσεις Article και UserController. Και οι δύο έχουν κάποιες εξαρτήσεις, δηλαδή τη βάση δεδομένων και το εργοστάσιο ArticleFactory. Και για αυτές τις κλάσεις, θα δημιουργήσουμε τώρα έναν περιέκτη. Φυσικά, για ένα τόσο απλό παράδειγμα, δεν έχει νόημα να έχουμε ένα δοχείο. Αλλά θα δημιουργήσουμε έναν για να δείξουμε πώς φαίνεται και πώς λειτουργεί.

Εδώ είναι ένας απλός σκληρά κωδικοποιημένος περιέκτης για το παραπάνω παράδειγμα:

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

Η χρήση θα έμοιαζε ως εξής:

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

Το δοχείο τα γνωρίζει όλα αυτά. Οι εξαρτήσεις εγχέονται αυτόματα από τον περιέκτη. Αυτή είναι η δύναμή του.

Μέχρι στιγμής, ο περιέκτης έχει τα πάντα κωδικοποιημένα. Οπότε κάνουμε το επόμενο βήμα και προσθέτουμε παραμέτρους για να κάνουμε τον περιέκτη πραγματικά χρήσιμο:

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

Οι έξυπνοι αναγνώστες μπορεί να έχουν παρατηρήσει ένα πρόβλημα. Κάθε φορά που παίρνω ένα αντικείμενο UserController, δημιουργείται επίσης μια νέα περίπτωση ArticleFactory και μια νέα βάση δεδομένων. Σίγουρα δεν το θέλουμε αυτό.

Έτσι προσθέτουμε μια μέθοδο getService() που θα επιστρέφει τα ίδια instances ξανά και ξανά:

class Container
{
	private array $services = [];

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

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

	// ...
}

Η πρώτη κλήση π.χ. της $container->getService('Database') θα έχει ως αποτέλεσμα η createDatabase() να δημιουργήσει ένα αντικείμενο βάσης δεδομένων, το οποίο θα αποθηκεύσει στον πίνακα $services και θα το επιστρέψει άμεσα στην επόμενη κλήση.

Τροποποιούμε επίσης το υπόλοιπο δοχείο ώστε να χρησιμοποιεί το getService():

class Container
{
	// ...

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

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

Παρεμπιπτόντως, ο όρος service αναφέρεται σε κάθε αντικείμενο που διαχειρίζεται ο περιέκτης. Εξ ου και το όνομα της μεθόδου getService().

Έγινε. Έχουμε ένα πλήρως λειτουργικό DI container! Και μπορούμε να το χρησιμοποιήσουμε:

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

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

Όπως μπορείτε να δείτε, δεν είναι δύσκολο να γράψετε ένα DIC. Είναι αξιοσημείωτο ότι τα ίδια τα αντικείμενα δεν γνωρίζουν ότι ένα δοχείο τα δημιουργεί. Έτσι, είναι δυνατόν να δημιουργηθεί οποιοδήποτε αντικείμενο στην PHP με αυτόν τον τρόπο χωρίς να επηρεαστεί ο πηγαίος κώδικάς τους.

Η χειροκίνητη δημιουργία και συντήρηση μιας κλάσης εμπορευματοκιβωτίου μπορεί να γίνει ένας εφιάλτης μάλλον γρήγορα. Επομένως, στο επόμενο κεφάλαιο θα μιλήσουμε για το Nette DI Container, το οποίο μπορεί να δημιουργεί και να ενημερώνεται σχεδόν αυτόματα.

έκδοση: 3.x