Αυτόματη καλωδίωση
Το 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 # μεταβιβάζει το παιδί της υπηρεσίας στον κατασκευαστή