Création d'extensions pour Nette DI
La génération du conteneur DI, en plus des fichiers de configuration, est également influencée par ce qu'on
appelle des extensions. Nous les activons dans le fichier de configuration dans la section extensions
.
De cette manière, nous ajoutons l'extension représentée par la classe BlogExtension
sous le nom
blog
:
extensions:
blog: BlogExtension
Chaque extension du compilateur hérite de Nette\DI\CompilerExtension et peut implémenter les méthodes suivantes, qui sont appelées séquentiellement lors de la construction du conteneur DI :
- getConfigSchema()
- loadConfiguration()
- beforeCompile()
- afterCompile()
getConfigSchema()
Cette méthode est appelée en premier. Elle définit le schéma pour la validation des paramètres de configuration.
Nous configurons l'extension dans la section dont le nom est le même que celui sous lequel l'extension a été ajoutée,
c'est-à-dire blog
:
# même nom que l'extension
blog:
postsPerPage: 10
allowComments: false
Nous créons un schéma décrivant toutes les options de configuration, y compris leurs types, les valeurs autorisées et éventuellement les valeurs par défaut :
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 documentation se trouve sur la page Schéma. De plus, il est possible de
spécifier quelles options peuvent être dynamiques à l'aide de dynamic()
,
par ex. Expect::int()->dynamic()
.
Nous accédons à la configuration via la variable $this->config
, qui est un objet stdClass
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$num = $this->config->postPerPage;
if ($this->config->allowComments) {
// ...
}
}
}
loadConfiguration()
Utilisé pour ajouter des services au conteneur. Pour cela, on utilise 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']) // ou setCreator()
->addSetup('setLogger', ['@logger']);
}
}
La convention est de préfixer les services ajoutés par l'extension avec son nom pour éviter les conflits de noms. C'est ce
que fait la méthode prefix()
, donc si l'extension s'appelle blog
, le service portera le nom
blog.articles
.
Si nous devons renommer un service, nous pouvons créer un alias avec le nom d'origine pour maintenir la compatibilité
ascendante. Nette fait de même, par exemple, pour le service routing.router
, qui est également disponible sous son
ancien nom router
.
$builder->addAlias('router', 'routing.router');
Chargement des services depuis un fichier
Nous n'avons pas besoin de créer des services uniquement à l'aide de l'API de la classe ContainerBuilder, mais aussi avec la
notation familière utilisée dans le fichier de configuration NEON dans la section services. Le préfixe @extension
représente l'extension actuelle.
services:
articles:
create: MyBlog\ArticlesModel(@connection)
comments:
create: MyBlog\CommentsModel(@connection, @extension.articles)
articlesList:
create: MyBlog\Components\ArticlesList(@extension.articles)
Nous chargeons les services :
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
// chargement du fichier de configuration pour l'extension
$this->compiler->loadDefinitionsFromConfig(
$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
);
}
}
beforeCompile()
La méthode est appelée lorsque le conteneur contient tous les services ajoutés par les extensions individuelles dans les
méthodes loadConfiguration
ainsi que par les fichiers de configuration utilisateur. À ce stade de la construction,
nous pouvons donc modifier les définitions de service ou ajouter des liens entre elles. Pour rechercher des services dans le
conteneur par tags, on peut utiliser la méthode findByTag()
, et par classe ou interface, la méthode
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()
À ce stade, la classe du conteneur est déjà générée sous forme d'objet ClassType, contient toutes les méthodes qui créent les services et est prête à être écrite dans le cache. Nous pouvons encore modifier le code résultant de la classe à ce moment.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$method = $class->getMethod('__construct');
// ...
}
}
$initialization
Après la création du conteneur, la classe
Configurator appelle le code d'initialisation, qui est créé en écrivant dans l'objet $this->initialization
à
l'aide de la méthode addBody().
Montrons un exemple de comment démarrer la session ou lancer des services qui ont le tag run
avec le code
d'initialisation :
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
// démarrage automatique de la session
if ($this->config->session->autoStart) {
$this->initialization->addBody('$this->getService("session")->start()');
}
// les services avec le tag run doivent être créés après l'instanciation du conteneur
$builder = $this->getContainerBuilder();
foreach ($builder->findByTag('run') as $name => $foo) {
$this->initialization->addBody('$this->getService(?);', [$name]);
}
}
}