Δημιουργία επεκτάσεων για το Nette DI
Η δημιουργία του DI container, εκτός από τα αρχεία διαμόρφωσης,
επηρεάζεται και από τις λεγόμενες επεκτάσεις. Τις ενεργοποιούμε
στο αρχείο διαμόρφωσης στην ενότητα extensions
.
Έτσι προσθέτουμε την επέκταση που αντιπροσωπεύεται από την κλάση
BlogExtension
με το όνομα blog
:
extensions:
blog: BlogExtension
Κάθε επέκταση του compiler κληρονομεί από το Nette\DI\CompilerExtension και μπορεί να υλοποιήσει τις ακόλουθες μεθόδους, οι οποίες καλούνται διαδοχικά κατά τη συναρμολόγηση του DI container:
- getConfigSchema()
- loadConfiguration()
- beforeCompile()
- afterCompile()
getConfigSchema()
Αυτή η μέθοδος καλείται πρώτη. Ορίζει το schema για την επικύρωση των παραμέτρων διαμόρφωσης.
Διαμορφώνουμε την επέκταση στην ενότητα της οποίας το όνομα είναι το
ίδιο με αυτό με το οποίο προστέθηκε η επέκταση, δηλαδή blog
:
# ίδιο όνομα με την επέκταση
blog:
postsPerPage: 10
allowComments: false
Δημιουργούμε ένα schema που περιγράφει όλες τις επιλογές διαμόρφωσης, συμπεριλαμβανομένων των τύπων τους, των επιτρεπόμενων τιμών και, ενδεχομένως, των προεπιλεγμένων τιμών τους:
use Nette\Schema\Expect;
class BlogExtension extends Nette\DI\CompilerExtension
{
public function getConfigSchema(): Nette\Schema\Schema
{
return Expect::structure([
'postsPerPage' => Expect::int(),
'allowComments' => Expect::bool()->default(true),
]);
}
}
Θα βρείτε την τεκμηρίωση στη σελίδα Schema.
Επιπλέον, μπορείτε να καθορίσετε ποιες επιλογές μπορούν να είναι δυναμικές
χρησιμοποιώντας το dynamic()
, π.χ. Expect::int()->dynamic()
.
Έχουμε πρόσβαση στη διαμόρφωση μέσω της μεταβλητής $this->config
,
η οποία είναι ένα αντικείμενο stdClass
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$num = $this->config->postPerPage;
if ($this->config->allowComments) {
// ...
}
}
}
loadConfiguration()
Χρησιμοποιείται για την προσθήκη υπηρεσιών στο container. Γι' αυτό χρησιμοποιείται το Nette\DI\ContainerBuilder:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$builder->addDefinition($this->prefix('articles'))
->setFactory(App\Model\HomepageArticles::class, ['@connection']) // or setCreator()
->addSetup('setLogger', ['@logger']);
}
}
Η σύμβαση είναι να προτάσσεται στις υπηρεσίες που προστίθενται από
την επέκταση το όνομά της, ώστε να μην προκύπτουν συγκρούσεις ονομάτων.
Αυτό το κάνει η μέθοδος prefix()
, οπότε αν η επέκταση ονομάζεται
blog
, η υπηρεσία θα ονομάζεται blog.articles
.
Εάν χρειαστεί να μετονομάσουμε μια υπηρεσία, μπορούμε, για λόγους
διατήρησης της συμβατότητας προς τα πίσω, να δημιουργήσουμε ένα
ψευδώνυμο (alias) με το αρχικό όνομα. Παρόμοια το κάνει το Nette, π.χ. για την
υπηρεσία routing.router
, η οποία είναι διαθέσιμη και με το προηγούμενο
όνομα router
.
$builder->addAlias('router', 'routing.router');
Φόρτωση υπηρεσιών από αρχείο
Δεν χρειάζεται να δημιουργούμε υπηρεσίες μόνο μέσω του API της κλάσης
ContainerBuilder, αλλά και με τη γνωστή σύνταξη που χρησιμοποιείται στο αρχείο
διαμόρφωσης NEON στην ενότητα services. Το πρόθεμα @extension
αντιπροσωπεύει την τρέχουσα επέκταση.
services:
articles:
create: MyBlog\ArticlesModel(@connection)
comments:
create: MyBlog\CommentsModel(@connection, @extension.articles)
articlesList:
create: MyBlog\Components\ArticlesList(@extension.articles)
Φορτώνουμε τις υπηρεσίες:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
// φόρτωση του αρχείου διαμόρφωσης για την επέκταση
$this->compiler->loadDefinitionsFromConfig(
$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
);
}
}
beforeCompile()
Η μέθοδος καλείται τη στιγμή που το container περιέχει όλες τις υπηρεσίες
που προστέθηκαν από τις μεμονωμένες επεκτάσεις στις μεθόδους
loadConfiguration
καθώς και από τα αρχεία διαμόρφωσης χρήστη. Σε αυτή τη
φάση της συναρμολόγησης, μπορούμε λοιπόν να τροποποιήσουμε τους
ορισμούς των υπηρεσιών ή να συμπληρώσουμε τις συνδέσεις μεταξύ τους.
Για την αναζήτηση υπηρεσιών στο container με βάση τα tags, μπορεί να
χρησιμοποιηθεί η μέθοδος findByTag()
, ενώ με βάση την κλάση ή το interface,
η μέθοδος findByType()
.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function beforeCompile()
{
$builder = $this->getContainerBuilder();
foreach ($builder->findByTag('logaware') as $serviceName => $tagValue) {
$builder->getDefinition($serviceName)->addSetup('setLogger');
}
}
}
afterCompile()
Σε αυτή τη φάση, η κλάση του container έχει ήδη δημιουργηθεί με τη μορφή αντικειμένου ClassType, περιέχει όλες τις μεθόδους που δημιουργούν τις υπηρεσίες και είναι έτοιμη για εγγραφή στην cache. Τον τελικό κώδικα της κλάσης μπορούμε ακόμα να τον τροποποιήσουμε σε αυτή τη στιγμή.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$method = $class->getMethod('__construct');
// ...
}
}
$initialization
Η κλάση Configurator, μετά τη δημιουργία του container, καλεί τον
κώδικα αρχικοποίησης, ο οποίος δημιουργείται με εγγραφή στο
αντικείμενο $this->initialization
χρησιμοποιώντας τη μέθοδο addBody().
Ας δείξουμε ένα παράδειγμα για το πώς, για παράδειγμα, με τον κώδικα
αρχικοποίησης να ξεκινήσουμε τη session ή να εκκινήσουμε υπηρεσίες που
έχουν το tag run
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
// αυτόματη εκκίνηση της session
if ($this->config->session->autoStart) {
$this->initialization->addBody('$this->getService("session")->start()');
}
// οι υπηρεσίες με tag run πρέπει να δημιουργηθούν μετά την παρουσίαση του container
$builder = $this->getContainerBuilder();
foreach ($builder->findByTag('run') as $name => $foo) {
$this->initialization->addBody('$this->getService(?);', [$name]);
}
}
}