Δημιουργημένα Factories
Το Nette DI μπορεί να δημιουργήσει αυτόματα κώδικα factory βάσει interfaces, εξοικονομώντας σας τη συγγραφή κώδικα.
Ένα factory είναι μια κλάση που παράγει και διαμορφώνει αντικείμενα. Τους περνάει δηλαδή και τις εξαρτήσεις τους. Μην το συγχέετε με το σχεδιαστικό πρότυπο factory method, το οποίο περιγράφει έναν συγκεκριμένο τρόπο χρήσης των factories και δεν σχετίζεται με αυτό το θέμα.
Πώς μοιάζει ένα τέτοιο factory, το δείξαμε στο εισαγωγικό κεφάλαιο:
class ArticleFactory
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
public function create(): Article
{
return new Article($this->db);
}
}
Το Nette DI μπορεί να δημιουργήσει αυτόματα τον κώδικα των factories. Το μόνο
που έχετε να κάνετε είναι να δημιουργήσετε ένα interface και το Nette DI θα
δημιουργήσει την υλοποίηση. Το interface πρέπει να έχει ακριβώς μία μέθοδο
με το όνομα create
και να δηλώνει τον τύπο επιστροφής:
interface ArticleFactory
{
function create(): Article;
}
Δηλαδή, το factory ArticleFactory
έχει μια μέθοδο create
, η οποία
δημιουργεί αντικείμενα Article
. Η κλάση Article
μπορεί να
μοιάζει κάπως έτσι:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
) {
}
}
Προσθέτουμε το factory στο αρχείο διαμόρφωσης:
services:
- ArticleFactory
Το Nette DI θα δημιουργήσει την αντίστοιχη υλοποίηση του factory.
Στον κώδικα που χρησιμοποιεί το factory, ζητάμε λοιπόν το αντικείμενο σύμφωνα με το interface και το Nette DI θα χρησιμοποιήσει τη δημιουργημένη υλοποίηση:
class UserController
{
public function __construct(
private ArticleFactory $articleFactory,
) {
}
public function foo()
{
// αφήνουμε το factory να δημιουργήσει το αντικείμενο
$article = $this->articleFactory->create();
}
}
Παραμετροποιημένο factory
Η μέθοδος του factory create
μπορεί να δέχεται παραμέτρους, τις
οποίες στη συνέχεια περνά στον κατασκευαστή. Ας συμπληρώσουμε, για
παράδειγμα, την κλάση Article
με το ID του συγγραφέα του άρθρου:
class Article
{
public function __construct(
private Nette\Database\Connection $db,
private int $authorId,
) {
}
}
Προσθέτουμε την παράμετρο και στο factory:
interface ArticleFactory
{
function create(int $authorId): Article;
}
Χάρη στο γεγονός ότι η παράμετρος στον κατασκευαστή και η παράμετρος στο factory ονομάζονται το ίδιο, το Nette DI τις περνά εντελώς αυτόματα.
Προηγμένος ορισμός
Ο ορισμός μπορεί να γραφτεί και σε πολυγραμμική μορφή
χρησιμοποιώντας το κλειδί implement
:
services:
articleFactory:
implement: ArticleFactory
Κατά τη σύνταξη με αυτόν τον μακρύτερο τρόπο, είναι δυνατόν να
αναφερθούν επιπλέον ορίσματα για τον κατασκευαστή στο κλειδί
arguments
και συμπληρωματική διαμόρφωση μέσω του setup
, όπως
και στις κανονικές υπηρεσίες.
Παράδειγμα: εάν η μέθοδος create()
δεν δεχόταν την παράμετρο
$authorId
, θα μπορούσαμε να δηλώσουμε μια σταθερή τιμή στη
διαμόρφωση, η οποία θα περνούσε στον κατασκευαστή του Article
:
services:
articleFactory:
implement: ArticleFactory
arguments:
authorId: 123
Ή αντίστροφα, εάν η create()
δεχόταν την παράμετρο $authorId
,
αλλά δεν ήταν μέρος του κατασκευαστή και περνούσε μέσω της μεθόδου
Article::setAuthorId()
, θα αναφερόμασταν σε αυτήν στην ενότητα
setup
:
services:
articleFactory:
implement: ArticleFactory
setup:
- setAuthorId($authorId)
Accessor
Το Nette μπορεί, εκτός από factories, να δημιουργεί και τα λεγόμενα accessors.
Πρόκειται για αντικείμενα με μια μέθοδο get()
, η οποία επιστρέφει
μια συγκεκριμένη υπηρεσία από το DI container. Η επανειλημμένη κλήση του
get()
επιστρέφει πάντα την ίδια παρουσία.
Οι accessors παρέχουν lazy-loading στις εξαρτήσεις. Ας υποθέσουμε ότι έχουμε
μια κλάση που καταγράφει σφάλματα σε μια ειδική βάση δεδομένων. Εάν
αυτή η κλάση λάμβανε τη σύνδεση με τη βάση δεδομένων ως εξάρτηση μέσω
του κατασκευαστή, η σύνδεση θα έπρεπε πάντα να δημιουργείται, παρόλο
που στην πράξη ένα σφάλμα εμφανίζεται μόνο σπάνια και, επομένως, τις
περισσότερες φορές η σύνδεση θα παρέμενε αχρησιμοποίητη. Αντί γι' αυτό,
η κλάση περνά έναν accessor και μόνο όταν κληθεί το get()
του,
δημιουργείται το αντικείμενο της βάσης δεδομένων:
Πώς να δημιουργήσετε έναν accessor; Αρκεί να γράψετε ένα interface και το Nette DI
θα δημιουργήσει την υλοποίηση. Το interface πρέπει να έχει ακριβώς μία
μέθοδο με το όνομα get
και να δηλώνει τον τύπο επιστροφής:
interface PDOAccessor
{
function get(): PDO;
}
Προσθέτουμε τον accessor στο αρχείο διαμόρφωσης, όπου ορίζεται επίσης η υπηρεσία που θα επιστρέφει:
services:
- PDOAccessor
- PDO(%dsn%, %user%, %password%)
Επειδή ο accessor επιστρέφει μια υπηρεσία τύπου PDO
και στη
διαμόρφωση υπάρχει μόνο μία τέτοια υπηρεσία, θα επιστρέφει ακριβώς
αυτήν. Εάν υπήρχαν περισσότερες υπηρεσίες αυτού του τύπου, θα
καθορίζαμε την επιστρεφόμενη υπηρεσία χρησιμοποιώντας το όνομα, π.χ.
- PDOAccessor(@db1)
.
Πολλαπλό factory/accessor
Τα factories και οι accessors μας μπορούσαν μέχρι τώρα πάντα να παράγουν ή να
επιστρέφουν μόνο ένα αντικείμενο. Ωστόσο, είναι πολύ εύκολο να
δημιουργηθούν και πολλαπλά factories συνδυασμένα με accessors. Το interface μιας
τέτοιας κλάσης θα περιέχει οποιονδήποτε αριθμό μεθόδων με ονόματα
create<name>()
και get<name>()
, π.χ.:
interface MultiFactory
{
function createArticle(): Article;
function getDb(): PDO;
}
Έτσι, αντί να περνάμε πολλά δημιουργημένα factories και accessors, περνάμε ένα πιο σύνθετο factory που μπορεί να κάνει περισσότερα.
Εναλλακτικά, αντί για πολλές μεθόδους, μπορούμε να χρησιμοποιήσουμε
το get()
με παράμετρο:
interface MultiFactoryAlt
{
function get($name): PDO;
}
Τότε ισχύει ότι το MultiFactory::getArticle()
κάνει το ίδιο πράγμα με το
MultiFactoryAlt::get('article')
. Ωστόσο, η εναλλακτική σύνταξη έχει το
μειονέκτημα ότι δεν είναι σαφές ποιες τιμές $name
υποστηρίζονται
και λογικά δεν είναι δυνατό στο interface να διακριθούν διαφορετικές τιμές
επιστροφής για διαφορετικά $name
.
Ορισμός με λίστα
Με αυτόν τον τρόπο μπορεί να οριστεί ένα πολλαπλό factory στη διαμόρφωση:
services:
- MultiFactory(
article: Article # ορίζει το createArticle()
db: PDO(%dsn%, %user%, %password%) # ορίζει το getDb()
)
Ή μπορούμε στον ορισμό του factory να αναφερθούμε σε υπάρχουσες υπηρεσίες μέσω αναφοράς:
services:
article: Article
- PDO(%dsn%, %user%, %password%)
- MultiFactory(
article: @article # ορίζει το createArticle()
db: @\PDO # ορίζει το getDb()
)
Ορισμός με tags
Η δεύτερη επιλογή είναι να χρησιμοποιήσουμε για τον ορισμό tags:
services:
- App\Core\RouterFactory::createRouter
- App\Model\DatabaseAccessor(
db1: @database.db1.explorer
)