Πέρασμα εξαρτήσεων

Τα επιχειρήματα, ή “εξαρτήσεις” στην ορολογία DI, μπορούν να μεταβιβαστούν σε κλάσεις με τους ακόλουθους κύριους τρόπους:

  • περνώντας από τον κατασκευαστή
  • πέρασμα μέσω μεθόδου (που ονομάζεται setter)
  • με τον καθορισμό μιας ιδιότητας
  • μέσω μεθόδου, σημείωσης ή χαρακτηριστικού inject

Θα παρουσιάσουμε τώρα τις διάφορες παραλλαγές με συγκεκριμένα παραδείγματα.

Έγχυση κατασκευαστή

Οι εξαρτήσεις περνούν ως ορίσματα στον κατασκευαστή κατά τη δημιουργία του αντικειμένου:

class MyClass
{
	private Cache $cache;

	public function __construct(Cache $cache)
	{
		$this->cache = $cache;
	}
}

$obj = new MyClass($cache);

Αυτή η μορφή είναι χρήσιμη για τις υποχρεωτικές εξαρτήσεις που η κλάση χρειάζεται οπωσδήποτε για να λειτουργήσει, καθώς χωρίς αυτές δεν μπορεί να δημιουργηθεί η περίπτωση.

Από την PHP 8.0, μπορούμε να χρησιμοποιήσουμε μια συντομότερη μορφή συμβολισμού που είναι λειτουργικά ισοδύναμη (constructor property promotion):

// PHP 8.0
class MyClass
{
	public function __construct(
		private Cache $cache,
	) {
	}
}

Από την PHP 8.1, μια ιδιότητα μπορεί να επισημανθεί με μια σημαία readonly που δηλώνει ότι τα περιεχόμενα της ιδιότητας δεν θα αλλάξουν:

// PHP 8.1
class MyClass
{
	public function __construct(
		private readonly Cache $cache,
	) {
	}
}

Το DI container περνάει τις εξαρτήσεις στον κατασκευαστή αυτόματα χρησιμοποιώντας την αυτόματη σύνδεση (autowiring). Τα επιχειρήματα που δεν μπορούν να περάσουν με αυτόν τον τρόπο (π.χ. συμβολοσειρές, αριθμοί, booleans) γράφονται στη διαμόρφωση.

Κόλαση του κατασκευαστή

Ο όρος κόλαση κατασκευαστών αναφέρεται σε μια κατάσταση όπου ένα παιδί κληρονομεί από μια γονική κλάση της οποίας ο κατασκευαστής απαιτεί εξαρτήσεις και το παιδί απαιτεί επίσης εξαρτήσεις. Πρέπει επίσης να αναλάβει και να μεταβιβάσει τις εξαρτήσεις του γονέα:

abstract class BaseClass
{
	private Cache $cache;

	public function __construct(Cache $cache)
	{
		$this->cache = $cache;
	}
}

final class MyClass extends BaseClass
{
	private Database $db;

	// ⛔ CONSTRUCTOR HELL
	public function __construct(Cache $cache, Database $db)
	{
		parent::__construct($cache);
		$this->db = $db;
	}
}

Το πρόβλημα εμφανίζεται όταν θέλουμε να αλλάξουμε τον κατασκευαστή της κλάσης BaseClass, για παράδειγμα όταν προστίθεται μια νέα εξάρτηση. Τότε πρέπει να τροποποιήσουμε και όλους τους κατασκευαστές των παιδιών. Το οποίο κάνει μια τέτοια τροποποίηση κόλαση.

Πώς να το αποτρέψετε αυτό; Η λύση είναι η προτεραιότητα της σύνθεσης έναντι της κληρονομικότητας.

Ας σχεδιάσουμε λοιπόν τον κώδικα με διαφορετικό τρόπο. Θα αποφύγουμε τις αφηρημένες κλάσεις Base*. Αντί το MyClass να παίρνει κάποια λειτουργικότητα κληρονομώντας από το BaseClass, θα έχει αυτή τη λειτουργικότητα περασμένη ως εξάρτηση:

final class SomeFunctionality
{
	private Cache $cache;

	public function __construct(Cache $cache)
	{
		$this->cache = $cache;
	}
}

final class MyClass
{
	private SomeFunctionality $sf;
	private Database $db;

	public function __construct(SomeFunctionality $sf, Database $db) // ✅
	{
		$this->sf = $sf;
		$this->db = $db;
	}
}

Έγχυση ρυθμιστή

Οι εξαρτήσεις μεταφέρονται με την κλήση μιας μεθόδου που τις αποθηκεύει σε μια ιδιωτική ιδιότητα. Η συνήθης σύμβαση ονοματοδοσίας για αυτές τις μεθόδους είναι της μορφής set*(), γι' αυτό και ονομάζονται setters, αλλά φυσικά μπορούν να ονομαστούν και αλλιώς.

class MyClass
{
	private Cache $cache;

	public function setCache(Cache $cache): void
	{
		$this->cache = $cache;
	}
}

$obj = new MyClass;
$obj->setCache($cache);

Η μέθοδος αυτή είναι χρήσιμη για προαιρετικές εξαρτήσεις που δεν είναι απαραίτητες για τη λειτουργία της κλάσης, καθώς δεν είναι εγγυημένο ότι το αντικείμενο θα τις λάβει πραγματικά (δηλαδή ότι ο χρήστης θα καλέσει τη μέθοδο).

Ταυτόχρονα, η μέθοδος αυτή επιτρέπει την επανειλημμένη κλήση του setter για την αλλαγή της εξάρτησης. Αν αυτό δεν είναι επιθυμητό, προσθέστε έναν έλεγχο στη μέθοδο ή, από την PHP 8.1, σημειώστε την ιδιότητα $cache με τη σημαία readonly.

class MyClass
{
	private Cache $cache;

	public function setCache(Cache $cache): void
	{
		if ($this->cache) {
			throw new RuntimeException('The dependency has already been set');
		}
		$this->cache = $cache;
	}
}

Η κλήση του setter ορίζεται στη διαμόρφωση του δοχείου DI στην ενότητα setup. Επίσης, εδώ χρησιμοποιείται η αυτόματη μεταβίβαση εξαρτήσεων από την αυτόματη σύνδεση:

services:
	-	create: MyClass
		setup:
			- setCache

Property Injection

Οι εξαρτήσεις περνούν απευθείας στην ιδιότητα:

class MyClass
{
	public Cache $cache;
}

$obj = new MyClass;
$obj->cache = $cache;

publicΩς εκ τούτου, δεν έχουμε κανέναν έλεγχο για το αν η μεταβιβαζόμενη εξάρτηση θα είναι πράγματι του καθορισμένου τύπου (αυτό ίσχυε πριν από την PHP 7.4) και χάνουμε τη δυνατότητα να αντιδράσουμε στη νέα ανατεθείσα εξάρτηση με το δικό μας κώδικα, για παράδειγμα για να αποτρέψουμε μεταγενέστερες αλλαγές. Ταυτόχρονα, η ιδιότητα γίνεται μέρος της δημόσιας διεπαφής της κλάσης, κάτι που μπορεί να μην είναι επιθυμητό.

Η ρύθμιση της μεταβλητής ορίζεται στη διαμόρφωση του δοχείου DI στην ενότητα setup:

services:
	-	create: MyClass
		setup:
			- $cache = @\Cache

Ένεση

Ενώ οι τρεις προηγούμενες μέθοδοι ισχύουν γενικά σε όλες τις αντικειμενοστραφείς γλώσσες, η έγχυση μέσω μεθόδου, σχολίου ή χαρακτηριστικού inject είναι ειδική για τους παρουσιαστές Nette. Συζητούνται σε ξεχωριστό κεφάλαιο.

Ποιον τρόπο να επιλέξω;

  • ο κατασκευαστής είναι κατάλληλος για τις υποχρεωτικές εξαρτήσεις που χρειάζεται η κλάση για να λειτουργήσει
  • ο setter, από την άλλη πλευρά, είναι κατάλληλος για προαιρετικές εξαρτήσεις ή εξαρτήσεις που μπορούν να αλλάξουν
  • οι δημόσιες μεταβλητές δεν συνιστώνται
έκδοση: 3.x