Αυτόματη καλωδίωση

Το Autowiring είναι ένα σπουδαίο χαρακτηριστικό που μπορεί να περάσει αυτόματα υπηρεσίες στον κατασκευαστή και σε άλλες μεθόδους, έτσι ώστε να μην χρειάζεται να τις γράψουμε καθόλου. Σας εξοικονομεί πολύ χρόνο.

Αυτό μας επιτρέπει να παραλείψουμε τη συντριπτική πλειοψηφία των επιχειρημάτων κατά τη συγγραφή των ορισμών υπηρεσιών. Αντί για:

services:
	articles: Model\ArticleRepository(@database, @cache.storage)

Απλά γράψτε:

services:
	articles: Model\ArticleRepository

Έτσι, η κλάση ArticleRepository πρέπει να οριστεί ως εξής:

namespace Model;

class ArticleRepository
{
	public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
	{}
}

Για να χρησιμοποιηθεί η αυτόματη καλωδίωση, πρέπει να υπάρχει μόνο μια υπηρεσία για κάθε τύπο στο δοχείο. Αν υπήρχαν περισσότερες, το autowiring δεν θα ήξερε ποια να περάσει και θα πετούσε μια εξαίρεση:

services:
	mainDb: PDO(%dsn%, %user%, %password%)
	tempDb: PDO('sqlite::memory:')
	articles: Model\ArticleRepository  # THROWS EXCEPTION, τόσο η mainDb όσο και η tempDb ταιριάζουν

Η λύση θα ήταν είτε να παρακάμψετε το autowiring και να δηλώσετε ρητά το όνομα της υπηρεσίας (π.χ. articles: Model\ArticleRepository(@mainDb)). Ωστόσο, είναι πιο βολικό να απενεργοποιήσετε την αυτόματη σύνδεση μιας υπηρεσίας ή να προτιμήσετε την πρώτη υπηρεσία.

Απενεργοποιημένη αυτόματη σύνδεση

Μπορείτε να απενεργοποιήσετε την αυτόματη καλωδίωση υπηρεσιών χρησιμοποιώντας την επιλογή autowired: no:

services:
	mainDb: PDO(%dsn%, %user%, %password%)

	tempDb:
		create: PDO('sqlite::memory:')
		autowired: false                 # αφαιρεί το tempDb από την αυτόματη σύνδεση

	articles: Model\ArticleRepository    # επομένως περνάει την mainDb στον κατασκευαστή

Η υπηρεσία articles δεν πετάει την εξαίρεση ότι υπάρχουν δύο αντίστοιχες υπηρεσίες τύπου PDO (δηλ. mainDb και tempDb) που μπορούν να περάσουν στον κατασκευαστή, επειδή βλέπει μόνο την υπηρεσία mainDb.

Η διαμόρφωση της αυτόματης καλωδίωσης στο Nette λειτουργεί διαφορετικά από ό,τι στο Symfony, όπου η επιλογή autowire: false λέει ότι η αυτόματη καλωδίωση δεν πρέπει να χρησιμοποιείται για τα ορίσματα του κατασκευαστή υπηρεσιών. Στη Nette, η αυτόματη σύνδεση χρησιμοποιείται πάντα, είτε για τα ορίσματα του κατασκευαστή είτε για οποιαδήποτε άλλη μέθοδο. Η επιλογή autowired: false λέει ότι το instance της υπηρεσίας δεν πρέπει να μεταβιβάζεται πουθενά με χρήση autowiring.

Προτιμώμενη αυτόματη σύνδεση

Εάν έχουμε περισσότερες υπηρεσίες του ίδιου τύπου και μία από αυτές έχει την επιλογή autowired, αυτή η υπηρεσία γίνεται η προτιμώμενη:

services:
	mainDb:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO    # το καθιστά προτιμώμενο

	tempDb:
		create: PDO('sqlite::memory:')

	articles: Model\ArticleRepository

Η υπηρεσία articles δεν απορρίπτει την εξαίρεση ότι υπάρχουν δύο αντίστοιχες υπηρεσίες PDO (δηλ. mainDb και tempDb), αλλά χρησιμοποιεί την προτιμώμενη υπηρεσία, δηλ. mainDb.

Συλλογή υπηρεσιών

Η αυτόματη καλωδίωση μπορεί επίσης να περάσει μια συστοιχία υπηρεσιών ενός συγκεκριμένου τύπου. Δεδομένου ότι η PHP δεν μπορεί να σημειώσει εγγενώς τον τύπο των στοιχείων του πίνακα, εκτός από τον τύπο array, πρέπει να προστεθεί ένα σχόλιο phpDoc με τον τύπο του στοιχείου όπως το ClassName[]:

namespace Model;

class ShipManager
{
	/**
	 * @param Shipper[] $shippers
	 */
	public function __construct(array $shippers)
	{}
}

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

Ο τύπος στο σχόλιο μπορεί επίσης να είναι της μορφής array<int, Class> ή list<Class>. Αν δεν μπορείτε να ελέγξετε τη μορφή του σχολίου phpDoc, μπορείτε να περάσετε έναν πίνακα υπηρεσιών απευθείας στη ρύθμιση παραμέτρων χρησιμοποιώντας την εντολή typed().

Scalar Arguments

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

class MySettings
{
	public function __construct(
		// readonly μπορεί να χρησιμοποιηθεί από την PHP 8.1
		public readonly bool $value,
	)
	{}
}

Δημιουργείτε μια υπηρεσία προσθέτοντας την στη διαμόρφωση:

services:
	- MySettings('any value')

Όλες οι τάξεις θα την ζητούν στη συνέχεια μέσω αυτόματης σύνδεσης.

Περιορισμός της αυτόματης καλωδίωσης

Για μεμονωμένες υπηρεσίες, η αυτόματη καλωδίωση μπορεί να περιοριστεί σε συγκεκριμένες κλάσεις ή διεπαφές.

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

Ας πάρουμε ένα παράδειγμα:

class ParentClass
{}

class ChildClass extends ParentClass
{}

class ParentDependent
{
	function __construct(ParentClass $obj)
	{}
}

class ChildDependent
{
	function __construct(ChildClass $obj)
	{}
}

Αν τα καταχωρίζαμε όλα ως υπηρεσίες, η αυτόματη καλωδίωση θα αποτύγχανε:

services:
	parent: ParentClass
	child: ChildClass
	parentDep: ParentDependent  # THROWS EXCEPTION, και οι δύο γονείς και τα παιδιά ταιριάζουν
	childDep: ChildDependent    # μεταβιβάζει την υπηρεσία 'child' στον κατασκευαστή

Η υπηρεσία parentDep πετάει την εξαίρεση Multiple services of type ParentClass found: parent, child επειδή τόσο η parent όσο και η child χωράνε στον κατασκευαστή της και η αυτόματη σύνδεση δεν μπορεί να αποφασίσει ποια θα επιλέξει.

Για την υπηρεσία child, μπορούμε επομένως να περιορίσουμε την αυτόματη σύνδεσή της στο ChildClass:

services:
	parent: ParentClass
	child:
		create: ChildClass
		autowired: ChildClass   # εναλλακτική λύση: 'autowired: self'

	parentDep: ParentDependent  # THROWS EXCEPTION, το 'παιδί' δεν μπορεί να είναι autowired
	childDep: ChildDependent    # μεταβιβάζει την υπηρεσία 'child' στον κατασκευαστή

Η υπηρεσία parentDep περνάει τώρα στον κατασκευαστή της υπηρεσίας parentDep, αφού είναι πλέον το μόνο αντικείμενο που ταιριάζει. Η υπηρεσία child δεν περνάει πλέον με αυτόματη σύνδεση. Ναι, η υπηρεσία child εξακολουθεί να είναι τύπου ParentClass, αλλά η συνθήκη περιορισμού που δίνεται για τον τύπο της παραμέτρου δεν ισχύει πλέον, δηλαδή δεν ισχύει πλέον ότι το ParentClass είναι υπερτύπος του ChildClass.

Στην περίπτωση του child, το autowired: ChildClass θα μπορούσε να γραφτεί ως autowired: self καθώς το self σημαίνει τρέχων τύπος υπηρεσίας.

Το κλειδί autowired μπορεί να περιλαμβάνει διάφορες κλάσεις και διεπαφές ως πίνακα:

autowired: [BarClass, FooInterface]

Ας προσπαθήσουμε να προσθέσουμε διεπαφές στο παράδειγμα:

interface FooInterface
{}

interface BarInterface
{}

class ParentClass implements FooInterface
{}

class ChildClass extends ParentClass implements BarInterface
{}

class FooDependent
{
	function __construct(FooInterface $obj)
	{}
}

class BarDependent
{
	function __construct(BarInterface $obj)
	{}
}

class ParentDependent
{
	function __construct(ParentClass $obj)
	{}
}

class ChildDependent
{
	function __construct(ChildClass $obj)
	{}
}

Όταν δεν περιορίζουμε την υπηρεσία child, θα χωρέσει στους κατασκευαστές όλων των κλάσεων FooDependent, BarDependent, ParentDependent και ChildDependent και η αυτόματη σύνδεση θα την περάσει εκεί.

Ωστόσο, αν περιορίσουμε την αυτόματη σύνδεσή της στο ChildClass χρησιμοποιώντας το autowired: ChildClass (ή το self), η αυτόματη σύνδεση την περνάει μόνο στον κατασκευαστή ChildDependent, επειδή απαιτεί ένα όρισμα τύπου ChildClass και το ChildClass είναι τύπου ChildClass. Κανένας άλλος τύπος που καθορίζεται για τις άλλες παραμέτρους δεν είναι υπερσύνολο του ChildClass, οπότε η υπηρεσία δεν περνάει.

Αν την περιορίσουμε στο ParentClass χρησιμοποιώντας το autowired: ParentClass, η αυτόματη σύνδεση θα την περάσει ξανά στον κατασκευαστή ChildDependent (αφού ο απαιτούμενος τύπος ChildClass είναι υπερσύνολο του ParentClass) και στον κατασκευαστή ParentDependent επίσης, αφού ο απαιτούμενος τύπος του ParentClass είναι επίσης αντίστοιχος.

Αν το περιορίσουμε στο FooInterface, θα συνεχίσει να μεταβιβάζεται αυτόνομα στο ParentDependent (ο απαιτούμενος τύπος ParentClass είναι υπερτύπος του FooInterface) και στο ChildDependent, αλλά επιπλέον στον κατασκευαστή FooDependent, αλλά όχι στο BarDependent, αφού το BarInterface δεν είναι υπερτύπος του FooInterface.

services:
	child:
		create: ChildClass
		autowired: FooInterface

	fooDep: FooDependent        # μεταβιβάζει το παιδί υπηρεσίας στον κατασκευαστή
	barDep: BarDependent        # THROWS EXCEPTION, καμία υπηρεσία δεν θα περνούσε
	parentDep: ParentDependent  # περνάει το παιδί της υπηρεσίας στον κατασκευαστή
	childDep: ChildDependent    # μεταβιβάζει το παιδί της υπηρεσίας στον κατασκευαστή
έκδοση: 3.x