Erstellen von Erweiterungen für Nette DI
Die Generierung des DI-Containers wird neben den Konfigurationsdateien auch durch sogenannte Erweiterungen
beeinflusst. Wir aktivieren sie in der Konfigurationsdatei im Abschnitt extensions
.
So fügen wir eine Erweiterung hinzu, die durch die Klasse BlogExtension
repräsentiert wird, unter dem Namen
blog
:
extensions:
blog: BlogExtension
Jede Compiler-Erweiterung erbt von Nette\DI\CompilerExtension und kann die folgenden Methoden implementieren, die während der Erstellung des DI-Containers nacheinander aufgerufen werden:
- getConfigSchema()
- loadConfiguration()
- beforeCompile()
- afterCompile()
getConfigSchema()
Diese Methode wird zuerst aufgerufen. Sie definiert das Schema zur Validierung der Konfigurationsparameter.
Wir konfigurieren die Erweiterung im Abschnitt, dessen Name mit dem Namen übereinstimmt, unter dem die Erweiterung
hinzugefügt wurde, also blog
:
# gleicher Name wie die Extension
blog:
postsPerPage: 10
allowComments: false
Wir erstellen ein Schema, das alle Konfigurationsoptionen beschreibt, einschließlich ihrer Typen, erlaubten Werte und gegebenenfalls auch Standardwerte:
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),
]);
}
}
Die Dokumentation finden Sie auf der Seite Schema. Zusätzlich kann festgelegt
werden, welche Optionen dynamisch sein
können, mittels dynamic()
, z.B. Expect::int()->dynamic()
.
Auf die Konfiguration greifen wir über die Variable $this->config
zu, die ein
stdClass
-Objekt ist:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$num = $this->config->postPerPage;
if ($this->config->allowComments) {
// ...
}
}
}
loadConfiguration()
Wird verwendet, um Dienste zum Container hinzuzufügen. Dazu dient 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']) // oder setCreator()
->addSetup('setLogger', ['@logger']);
}
}
Die Konvention ist, Dienste, die durch eine Erweiterung hinzugefügt werden, mit deren Namen zu präfixieren, um
Namenskonflikte zu vermeiden. Dies macht die Methode prefix()
, sodass, wenn die Erweiterung blog
heißt,
der Dienst den Namen blog.articles
trägt.
Wenn wir einen Dienst umbenennen müssen, können wir aus Gründen der Abwärtskompatibilität einen Alias mit dem
ursprünglichen Namen erstellen. Ähnlich macht es Nette z. B. beim Dienst routing.router
, der auch unter dem
früheren Namen router
verfügbar ist.
$builder->addAlias('router', 'routing.router');
Laden von Diensten aus einer Datei
Dienste müssen nicht nur über die API der ContainerBuilder-Klasse erstellt werden, sondern auch mit der bekannten
Schreibweise, die in der NEON-Konfigurationsdatei im Abschnitt services
verwendet wird. Das Präfix
@extension
repräsentiert die aktuelle Extension.
services:
articles:
create: MyBlog\ArticlesModel(@connection)
comments:
create: MyBlog\CommentsModel(@connection, @extension.articles)
articlesList:
create: MyBlog\Components\ArticlesList(@extension.articles)
Wir laden die Dienste:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
// Laden der Konfigurationsdatei für die Erweiterung
$this->compiler->loadDefinitionsFromConfig(
$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
);
}
}
beforeCompile()
Die Methode wird aufgerufen, wenn der Container alle Dienste enthält, die von den einzelnen Erweiterungen in den
loadConfiguration
-Methoden sowie durch die Benutzer-Konfigurationsdateien hinzugefügt wurden. In dieser Phase der
Erstellung können wir also die Dienstdefinitionen ändern oder Abhängigkeiten zwischen ihnen hinzufügen. Zum Suchen von
Diensten im Container nach Tags kann die Methode findByTag()
verwendet werden, nach Klasse oder Schnittstelle die
Methode 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 dieser Phase ist die Containerklasse bereits in Form eines ClassType-Objekts generiert, enthält alle Methoden, die Dienste erstellen, und ist bereit, in den Cache geschrieben zu werden. Der resultierende Klassencode kann zu diesem Zeitpunkt noch geändert werden.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$method = $class->getMethod('__construct');
// ...
}
}
$initialization
Die Configurator-Klasse ruft nach der Erstellung des
Containers Initialisierungscode auf, der durch Schreiben in das $this->initialization
-Objekt mittels der Methode addBody() erstellt wird.
Wir zeigen ein Beispiel, wie man z. B. mit Initialisierungscode die Session startet oder Dienste startet, die das Tag
run
haben:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
// automatisches Starten der Session
if ($this->config->session->autoStart) {
$this->initialization->addBody('$this->getService("session")->start()');
}
// Dienste mit dem Tag run müssen nach der Instanziierung des Containers erstellt werden
$builder = $this->getContainerBuilder();
foreach ($builder->findByTag('run') as $name => $foo) {
$this->initialization->addBody('$this->getService(?);', [$name]);
}
}
}