Autowiring
Το Autowiring είναι ένα εξαιρετικό χαρακτηριστικό που μπορεί να περάσει αυτόματα τις απαιτούμενες υπηρεσίες στον κατασκευαστή και σε άλλες μεθόδους, οπότε δεν χρειάζεται να τις γράψουμε καθόλου. Σας εξοικονομεί πολύ χρόνο.
Χάρη σε αυτό, μπορούμε να παραλείψουμε τη συντριπτική πλειοψηφία των ορισμάτων κατά τη σύνταξη ορισμών υπηρεσιών. Αντί για:
services:
articles: Model\ArticleRepository(@database, @cache.storage)
Αρκεί να γράψουμε:
services:
articles: Model\ArticleRepository
Το Autowiring καθοδηγείται από τους τύπους, οπότε για να λειτουργήσει, η
κλάση ArticleRepository
πρέπει να οριστεί κάπως έτσι:
namespace Model;
class ArticleRepository
{
public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
{}
}
Για να είναι δυνατή η χρήση του autowiring, πρέπει να υπάρχει ακριβώς μία υπηρεσία για κάθε τύπο στο container. Αν υπήρχαν περισσότερες, το autowiring δεν θα ήξερε ποια να περάσει και θα προκαλούσε εξαίρεση:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb: PDO('sqlite::memory:')
articles: Model\ArticleRepository # ΠΡΟΚΑΛΕΙ ΕΞΑΙΡΕΣΗ, ταιριάζουν και η mainDb και η tempDb
Η λύση θα ήταν είτε να παρακάμψουμε το autowiring και να δηλώσουμε ρητά το
όνομα της υπηρεσίας (δηλ. articles: Model\ArticleRepository(@mainDb)
). Πιο έξυπνο
όμως είναι να απενεργοποιήσουμε το autowiring για μία
από τις υπηρεσίες, ή να δώσουμε προτεραιότητα
στην πρώτη υπηρεσία.
Απενεργοποίηση του autowiring
Μπορούμε να απενεργοποιήσουμε το autowiring μιας υπηρεσίας
χρησιμοποιώντας την επιλογή autowired: no
:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
create: PDO('sqlite::memory:')
autowired: false # η υπηρεσία tempDb εξαιρείται από το autowiring
articles: Model\ArticleRepository # επομένως περνάει τη mainDb στον κατασκευαστή
Η υπηρεσία articles
δεν προκαλεί εξαίρεση ότι υπάρχουν δύο
κατάλληλες υπηρεσίες τύπου PDO
(δηλ. mainDb
και tempDb
)
που μπορούν να περάσουν στον κατασκευαστή, επειδή βλέπει μόνο την
υπηρεσία mainDb
.
Η διαμόρφωση του autowiring στο Nette λειτουργεί διαφορετικά από ό,τι
στο Symfony, όπου η επιλογή autowire: false
λέει ότι το autowiring δεν πρέπει να
χρησιμοποιείται για τα ορίσματα του κατασκευαστή της συγκεκριμένης
υπηρεσίας. Στο Nette, το autowiring χρησιμοποιείται πάντα, είτε για τα ορίσματα
του κατασκευαστή, είτε για οποιαδήποτε άλλη μέθοδο. Η επιλογή
autowired: false
λέει ότι η παρουσία της συγκεκριμένης υπηρεσίας δεν
πρέπει να περνιέται πουθενά μέσω autowiring.
Προτίμηση autowiring
Εάν έχουμε πολλές υπηρεσίες του ίδιου τύπου και σε μία από αυτές
δηλώσουμε την επιλογή autowired
, αυτή η υπηρεσία γίνεται η
προτιμώμενη:
services:
mainDb:
create: PDO(%dsn%, %user%, %password%)
autowired: PDO # γίνεται η προτιμώμενη
tempDb:
create: PDO('sqlite::memory:')
articles: Model\ArticleRepository
Η υπηρεσία articles
δεν προκαλεί εξαίρεση ότι υπάρχουν δύο
κατάλληλες υπηρεσίες τύπου PDO
(δηλ. mainDb
και tempDb
),
αλλά χρησιμοποιεί την προτιμώμενη υπηρεσία, δηλαδή τη mainDb
.
Πίνακας υπηρεσιών
Το Autowiring μπορεί επίσης να περάσει πίνακες υπηρεσιών ενός
συγκεκριμένου τύπου. Επειδή στην PHP δεν είναι δυνατό να γραφτεί εγγενώς
ο τύπος των στοιχείων του πίνακα, είναι απαραίτητο, εκτός από τον τύπο
array
, να συμπληρωθεί και ένα phpDoc σχόλιο με τον τύπο του στοιχείου
στη μορφή ClassName[]
:
namespace Model;
class ShipManager
{
/**
* @param Shipper[] $shippers
*/
public function __construct(array $shippers)
{}
}
Το DI container στη συνέχεια περνά αυτόματα έναν πίνακα υπηρεσιών που αντιστοιχούν στον συγκεκριμένο τύπο. Παραλείπει τις υπηρεσίες που έχουν απενεργοποιημένο το autowiring.
Ο τύπος στο σχόλιο μπορεί επίσης να είναι στη μορφή
array<int, Class>
ή list<Class>
. Εάν δεν μπορείτε να επηρεάσετε
τη μορφή του phpDoc σχολίου, μπορείτε να περάσετε τον πίνακα υπηρεσιών
απευθείας στη διαμόρφωση χρησιμοποιώντας το typed()
.
Σκαλωτά ορίσματα
Το Autowiring μπορεί να αντικαταστήσει μόνο αντικείμενα και πίνακες αντικειμένων. Τα σκαλωτά ορίσματα (π.χ. συμβολοσειρές, αριθμοί, booleans) τα γράφουμε στη διαμόρφωση. Μια εναλλακτική λύση είναι να δημιουργήσετε ένα settings-object, το οποίο ενσωματώνει την σκαλωτή τιμή (ή περισσότερες τιμές) σε μορφή αντικειμένου, το οποίο στη συνέχεια μπορεί να περάσει ξανά μέσω autowiring.
class MySettings
{
public function __construct(
// το readonly είναι δυνατό να χρησιμοποιηθεί από την PHP 8.1
public readonly bool $value,
)
{}
}
Δημιουργείτε μια υπηρεσία από αυτό προσθέτοντάς το στη διαμόρφωση:
services:
- MySettings('any value')
Όλες οι κλάσεις στη συνέχεια το ζητούν μέσω autowiring.
Περιορισμός του autowiring
Για μεμονωμένες υπηρεσίες, το autowiring μπορεί να περιοριστεί μόνο σε συγκεκριμένες κλάσεις ή interfaces.
Κανονικά, το autowiring περνά την υπηρεσία σε κάθε παράμετρο μεθόδου, ο τύπος της οποίας αντιστοιχεί στην υπηρεσία. Ο περιορισμός σημαίνει ότι θέτουμε συνθήκες που πρέπει να πληρούν οι τύποι που αναφέρονται στις παραμέτρους των μεθόδων, ώστε η υπηρεσία να τους περάσει.
Ας το δείξουμε με ένα παράδειγμα:
class ParentClass
{}
class ChildClass extends ParentClass
{}
class ParentDependent
{
function __construct(ParentClass $obj)
{}
}
class ChildDependent
{
function __construct(ChildClass $obj)
{}
}
Αν τις καταχωρούσαμε όλες ως υπηρεσίες, το autowiring θα αποτύγχανε:
services:
parent: ParentClass
child: ChildClass
parentDep: ParentDependent # ΠΡΟΚΑΛΕΙ ΕΞΑΙΡΕΣΗ, ταιριάζουν οι υπηρεσίες parent και child
childDep: ChildDependent # το autowiring περνά την υπηρεσία child στον κατασκευαστή
Η υπηρεσία parentDep
προκαλεί εξαίρεση
Multiple services of type ParentClass found: parent, child
, επειδή στον κατασκευαστή της
ταιριάζουν και οι δύο υπηρεσίες parent
και child
, και το autowiring
δεν μπορεί να αποφασίσει ποια να επιλέξει.
Για την υπηρεσία child
, μπορούμε επομένως να περιορίσουμε το
autowiring της στον τύπο ChildClass
:
services:
parent: ParentClass
child:
create: ChildClass
autowired: ChildClass # μπορεί να γραφτεί και 'autowired: self'
parentDep: ParentDependent # το autowiring περνά την υπηρεσία parent στον κατασκευαστή
childDep: ChildDependent # το autowiring περνά την υπηρεσία child στον κατασκευαστή
Τώρα, στον κατασκευαστή της υπηρεσίας parentDep
περνιέται η
υπηρεσία parent
, επειδή τώρα είναι το μόνο κατάλληλο αντικείμενο.
Το autowiring δεν περνά πλέον την υπηρεσία child
εκεί. Ναι, η υπηρεσία
child
εξακολουθεί να είναι τύπου ParentClass
, αλλά η
περιοριστική συνθήκη που δόθηκε για τον τύπο της παραμέτρου δεν ισχύει
πλέον, δηλ. δεν ισχύει ότι το ParentClass
είναι υπερτύπος του
ChildClass
.
Για την υπηρεσία child
, το autowired: ChildClass
θα μπορούσε επίσης
να γραφτεί ως autowired: self
, καθώς το self
είναι ένα placeholder για
την κλάση της τρέχουσας υπηρεσίας.
Στο κλειδί autowired
, είναι δυνατόν να αναφερθούν και πολλές
κλάσεις ή interfaces ως πίνακας:
autowired: [BarClass, FooInterface]
Ας δοκιμάσουμε να συμπληρώσουμε το παράδειγμα και με interfaces:
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
και το autowiring θα την περάσει εκεί.
Αν όμως περιορίσουμε το autowiring της σε ChildClass
χρησιμοποιώντας
autowired: ChildClass
(ή self
), το autowiring θα την περάσει μόνο στον
κατασκευαστή του ChildDependent
, επειδή απαιτεί όρισμα τύπου
ChildClass
και ισχύει ότι το ChildClass
είναι τύπου
ChildClass
. Κανένας άλλος τύπος που αναφέρεται στις άλλες
παραμέτρους δεν είναι υπερτύπος του ChildClass
, οπότε η υπηρεσία δεν
περνιέται.
Αν το περιορίσουμε σε ParentClass
χρησιμοποιώντας
autowired: ParentClass
, το autowiring θα την περάσει ξανά στον κατασκευαστή του
ChildDependent
(επειδή το απαιτούμενο ChildClass
είναι υπερτύπος του
ParentClass
) και τώρα και στον κατασκευαστή του ParentDependent
,
επειδή ο απαιτούμενος τύπος ParentClass
είναι επίσης κατάλληλος.
Αν το περιορίσουμε σε FooInterface
, θα εξακολουθεί να γίνεται autowired
στο ParentDependent
(το απαιτούμενο ParentClass
είναι υπερτύπος του
FooInterface
) και στο ChildDependent
, αλλά επιπλέον και στον
κατασκευαστή του FooDependent
, όχι όμως στο BarDependent
, επειδή το
BarInterface
δεν είναι υπερτύπος του FooInterface
.
services:
child:
create: ChildClass
autowired: FooInterface
fooDep: FooDependent # το autowiring περνά το child στον κατασκευαστή
barDep: BarDependent # ΠΡΟΚΑΛΕΙ ΕΞΑΙΡΕΣΗ, καμία υπηρεσία δεν ταιριάζει
parentDep: ParentDependent # το autowiring περνά το child στον κατασκευαστή
childDep: ChildDependent # το autowiring περνά το child στον κατασκευαστή