Création d'extensions pour Nette DI
La génération d'un conteneur DI, en plus des fichiers de configuration, affecte également ce que l'on appelle
les extensions. Nous les activons dans le fichier de configuration dans la section extensions
.
C'est ainsi que nous ajoutons l'extension représentée par la classe BlogExtension
avec le nom
blog
:
extensions:
blog: BlogExtension
Chaque extension de compilateur hérite de Nette\DI\CompilerExtension et peut implémenter les méthodes suivantes qui sont appelées pendant la compilation DI :
- getConfigSchema()
- loadConfiguration()
- beforeCompile()
- afterCompile()
getConfigSchema()
Cette méthode est appelée en premier. Elle définit le schéma utilisé pour valider les paramètres de configuration.
Les extensions sont configurées dans une section dont le nom est le même que celui sous lequel l'extension a été ajoutée,
par exemple blog
.
# même nom que mon extension
blog:
postsPerPage: 10
comments: false
Nous allons définir un schéma décrivant toutes les options de configuration, y compris leurs types, les valeurs accepté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),
]);
}
}
Voir le schéma pour la documentation. En outre, vous pouvez spécifier quelles
options peuvent être dynamiques en utilisant
dynamic()
, par exemple Expect::int()->dynamic()
.
On accède à la configuration par le biais de $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()
Cette méthode est utilisée pour ajouter des services au conteneur. Ceci est fait par 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 consiste à préfixer les services ajoutés par une extension avec son nom afin d'éviter tout conflit de noms.
C'est ce que fait prefix()
, donc si l'extension s'appelle “blog”, le service s'appellera
blog.articles
.
Si nous devons renommer un service, nous pouvons créer un alias avec son nom d'origine pour maintenir la compatibilité
ascendante. C'est ce que Nette fait par exemple pour routing.router
, qui est également disponible sous l'ancien nom
router
.
$builder->addAlias('router', 'routing.router');
Récupérer les services d'un fichier
Nous pouvons créer des services à l'aide de l'API ContainerBuilder, mais nous pouvons également les ajouter via le fichier
de configuration NEON bien connu et sa 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 allons ajouter des services de cette manière :
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
// charge le fichier de configuration de l'extension
$this->compiler->loadDefinitionsFromConfig(
$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
);
}
}
beforeCompile()
Cette 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 les fichiers de configuration de l'utilisateur. À cette phase d'assemblage,
nous pouvons alors modifier les définitions des services ou ajouter des liens entre eux. Vous pouvez utiliser la méthode
findByTag()
pour rechercher des services par balises, ou la méthode findByType()
pour rechercher par
classe ou interface.
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 conteneur est déjà générée en tant qu'objet ClassType, elle contient toutes les méthodes créées par le service et est prête à être mise en cache sous forme de fichier PHP. Nous pouvons encore modifier le code de la classe résultante à ce stade.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$method = $class->getMethod('__construct');
// ...
}
}
$initialisation
Le configurateur appelle le code d'initialisation après la création du conteneur, qui est créé en écrivant dans
un objet $this->initialization
à l'aide de la méthode addBody().
Nous allons montrer un exemple de la façon de démarrer une session ou de lancer des services qui ont la balise
run
en utilisant 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 l'étiquette "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]);
}
}
}