Creazione di estensioni per Nette DI
La generazione del container DI, oltre ai file di configurazione, è influenzata anche dalle cosiddette
estensioni. Le attiviamo nel file di configurazione nella sezione extensions
.
In questo modo aggiungiamo l'estensione rappresentata dalla classe BlogExtension
con il nome
blog
:
extensions:
blog: BlogExtension
Ogni estensione del compilatore eredita da Nette\DI\CompilerExtension e può implementare i seguenti metodi, che vengono chiamati in sequenza durante la costruzione del container DI:
- getConfigSchema()
- loadConfiguration()
- beforeCompile()
- afterCompile()
getConfigSchema()
Questo metodo viene chiamato per primo. Definisce lo schema per la validazione dei parametri di configurazione.
Configuriamo l'estensione nella sezione il cui nome è lo stesso di quello con cui è stata aggiunta l'estensione, cioè
blog
:
# stesso nome dell'estensione
blog:
postsPerPage: 10
allowComments: false
Creiamo uno schema che descrive tutte le opzioni di configurazione, inclusi i loro tipi, valori consentiti ed eventualmente anche valori predefiniti:
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),
]);
}
}
La documentazione si trova nella pagina Schema. Inoltre, è possibile specificare
quali opzioni possono essere dinamiche usando
dynamic()
, ad es. Expect::int()->dynamic()
.
Accediamo alla configurazione tramite la variabile $this->config
, che è un oggetto stdClass
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$num = $this->config->postPerPage;
if ($this->config->allowComments) {
// ...
}
}
}
loadConfiguration()
Utilizzato per aggiungere servizi al container. A questo serve 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']);
}
}
La convenzione è di prefissare i servizi aggiunti dall'estensione con il suo nome, per evitare conflitti di nomi. Questo lo
fa il metodo prefix()
, quindi se l'estensione si chiama blog
, il servizio si chiamerà
blog.articles
.
Se dobbiamo rinominare un servizio, possiamo creare un alias con il nome originale per mantenere la compatibilità
all'indietro. Nette fa qualcosa di simile, ad esempio, con il servizio routing.router
, che è disponibile anche con
il nome precedente router
.
$builder->addAlias('router', 'routing.router');
Caricamento dei servizi da file
Non dobbiamo creare servizi solo tramite l'API della classe ContainerBuilder, ma anche con la nota sintassi utilizzata nel file
di configurazione NEON nella sezione services. Il prefisso @extension
rappresenta l'estensione corrente.
services:
articles:
create: MyBlog\ArticlesModel(@connection)
comments:
create: MyBlog\CommentsModel(@connection, @extension.articles)
articlesList:
create: MyBlog\Components\ArticlesList(@extension.articles)
Carichiamo i servizi:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
// caricamento del file di configurazione per l'estensione
$this->compiler->loadDefinitionsFromConfig(
$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
);
}
}
beforeCompile()
Il metodo viene chiamato nel momento in cui il container contiene tutti i servizi aggiunti dalle singole estensioni nei metodi
loadConfiguration
e anche dai file di configurazione utente. In questa fase di costruzione, possiamo quindi
modificare le definizioni dei servizi o aggiungere legami tra di essi. Per cercare servizi nel container in base ai tag, si può
utilizzare il metodo findByTag()
, per classe o interfaccia invece il metodo 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()
In questa fase, la classe del container è già generata sotto forma di oggetto ClassType, contiene tutti i metodi che creano i servizi ed è pronta per essere scritta nella cache. Possiamo ancora modificare il codice risultante della classe in questo momento.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$method = $class->getMethod('__construct');
// ...
}
}
$initialization
La classe Configurator, dopo la creazione del
container, chiama il codice di inizializzazione, che viene creato scrivendo nell'oggetto $this->initialization
tramite il metodo addBody().
Mostriamo un esempio di come, ad esempio, avviare la sessione con il codice di inizializzazione o avviare servizi che hanno il
tag run
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
// avvio automatico della sessione
if ($this->config->session->autoStart) {
$this->initialization->addBody('$this->getService("session")->start()');
}
// i servizi con il tag run devono essere creati dopo l'istanza del container
$builder = $this->getContainerBuilder();
foreach ($builder->findByTag('run') as $name => $foo) {
$this->initialization->addBody('$this->getService(?);', [$name]);
}
}
}