Definition von Diensten
Die Konfiguration ist der Ort, an dem wir dem DI-Container beibringen, wie er einzelne Dienste erstellen und sie mit anderen Abhängigkeiten verbinden soll. Nette bietet eine sehr übersichtliche und elegante Möglichkeit, dies zu erreichen.
Der Abschnitt services
in der Konfigurationsdatei im NEON-Format ist der Ort, an dem wir eigene Dienste und ihre
Konfigurationen definieren. Sehen wir uns ein einfaches Beispiel für die Definition eines Dienstes namens database
an, der eine Instanz der Klasse PDO
repräsentiert:
services:
database: PDO('sqlite::memory:')
Die angegebene Konfiguration führt zu folgender Factory-Methode im DI-Container:
public function createServiceDatabase(): PDO
{
return new PDO('sqlite::memory:');
}
Dienstnamen ermöglichen es uns, uns in anderen Teilen der Konfigurationsdatei im Format @dienstName
darauf zu
beziehen. Wenn es nicht notwendig ist, den Dienst zu benennen, können wir einfach einen Bindestrich verwenden:
services:
- PDO('sqlite::memory:')
Um einen Dienst aus dem DI-Container zu erhalten, können wir die Methode getService()
mit dem Dienstnamen als
Parameter oder die Methode getByType()
mit dem Diensttyp verwenden:
$database = $container->getService('database');
$database = $container->getByType(PDO::class);
Erstellung eines Dienstes
Meistens erstellen wir einen Dienst einfach durch Instanziierung einer bestimmten Klasse. Zum Beispiel:
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Wenn wir die Konfiguration um weitere Schlüssel erweitern müssen, kann die Definition auf mehrere Zeilen aufgeteilt werden:
services:
database:
create: PDO('sqlite::memory:')
setup: ...
Der Schlüssel create
hat den Alias factory
, beide Varianten sind in der Praxis üblich. Wir
empfehlen jedoch die Verwendung von create
.
Die Argumente des Konstruktors oder der Erstellungsmethode können alternativ im Schlüssel arguments
angegeben
werden:
services:
database:
create: PDO
arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]
Dienste müssen nicht nur durch einfache Instanziierung einer Klasse erstellt werden, sie können auch das Ergebnis des Aufrufs statischer Methoden oder Methoden anderer Dienste sein:
services:
database: DatabaseFactory::create()
router: @routerFactory::create()
Beachten Sie, dass zur Vereinfachung anstelle von ->
das Zeichen ::
verwendet wird, siehe Ausdrucksmittel. Es werden diese Factory-Methoden generiert:
public function createServiceDatabase(): PDO
{
return DatabaseFactory::create();
}
public function createServiceRouter(): RouteList
{
return $this->getService('routerFactory')->create();
}
Der DI-Container muss den Typ des erstellten Dienstes kennen. Wenn wir einen Dienst mit einer Methode erstellen, die keinen spezifizierten Rückgabetyp hat, müssen wir diesen Typ explizit in der Konfiguration angeben:
services:
database:
create: DatabaseFactory::create()
type: PDO
Argumente
Wir übergeben Argumente an den Konstruktor und Methoden auf eine Weise, die dem Vorgehen in PHP selbst sehr ähnlich ist:
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Zur besseren Lesbarkeit können wir die Argumente auf separate Zeilen aufteilen. In diesem Fall ist die Verwendung von Kommas optional:
services:
database: PDO(
'mysql:host=127.0.0.1;dbname=test'
root
secret
)
Sie können Argumente auch benennen und müssen sich dann nicht um ihre Reihenfolge kümmern:
services:
database: PDO(
username: root
password: secret
dsn: 'mysql:host=127.0.0.1;dbname=test'
)
Wenn Sie einige Argumente auslassen und ihren Standardwert verwenden oder einen Dienst mittels Autowiring einsetzen möchten, verwenden Sie einen Unterstrich
_
:
services:
foo: Foo(_, %appDir%)
Als Argumente können Dienste übergeben, Parameter verwendet und vieles mehr getan werden, siehe Ausdrucksmittel.
Setup
Im Abschnitt setup
definieren wir Methoden, die beim Erstellen des Dienstes aufgerufen werden sollen.
services:
database:
create: PDO(%dsn%, %user%, %password%)
setup:
- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)
Das würde in PHP so aussehen:
public function createServiceDatabase(): PDO
{
$service = new PDO('...', '...', '...');
$service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $service;
}
Neben dem Aufruf von Methoden können auch Werte an Eigenschaften übergeben werden. Das Hinzufügen eines Elements zu einem Array wird ebenfalls unterstützt, was in Anführungszeichen geschrieben werden muss, um nicht mit der NEON-Syntax zu kollidieren:
services:
foo:
create: Foo
setup:
- $value = 123
- '$onClick[]' = [@bar, clickHandler]
Was im PHP-Code folgendermaßen aussehen würde:
public function createServiceFoo(): Foo
{
$service = new Foo;
$service->value = 123;
$service->onClick[] = [$this->getService('bar'), 'clickHandler'];
return $service;
}
Im Setup können jedoch auch statische Methoden oder Methoden anderer Dienste aufgerufen werden. Wenn Sie den aktuellen Dienst
als Argument übergeben müssen, geben Sie ihn als @self
an:
services:
foo:
create: Foo
setup:
- My\Helpers::initializeFoo(@self)
- @anotherService::setFoo(@self)
Beachten Sie, dass zur Vereinfachung anstelle von ->
das Zeichen ::
verwendet wird, siehe Ausdrucksmittel. Es wird eine solche Factory-Methode generiert:
public function createServiceFoo(): Foo
{
$service = new Foo;
My\Helpers::initializeFoo($service);
$this->getService('anotherService')->setFoo($service);
return $service;
}
Ausdrucksmittel
Nette DI bietet uns außergewöhnlich reichhaltige Ausdrucksmittel, mit denen wir fast alles schreiben können. In Konfigurationsdateien können wir daher Parameter verwenden:
# Parameter
%wwwDir%
# Wert des Parameters unter dem Schlüssel
%mailer.user%
# Parameter innerhalb einer Zeichenkette
'%wwwDir%/images'
Weiterhin Objekte erstellen, Methoden und Funktionen aufrufen:
# Objekt erstellen
DateTime()
# statische Methode aufrufen
Collator::create(%locale%)
# PHP-Funktion aufrufen
::getenv(DB_USER)
Auf Dienste entweder nach ihrem Namen oder nach Typ verweisen:
# Dienst nach Namen
@database
# Dienst nach Typ
@Nette\Database\Connection
Verwenden Sie die First-Class-Callable-Syntax:
# Callback erstellen, Äquivalent zu [@user, logout]
@user::logout(...)
Konstanten verwenden:
# Klassenkonstante
FilesystemIterator::SKIP_DOTS
# globale Konstante erhalten wir mit der PHP-Funktion constant()
::constant(PHP_VERSION)
Methodenaufrufe können wie in PHP verkettet werden. Nur zur Vereinfachung wird anstelle von ->
das Zeichen
::
verwendet:
DateTime()::format('Y-m-d')
# PHP: (new DateTime())->format('Y-m-d')
@http.request::getUrl()::getHost()
# PHP: $this->getService('http.request')->getUrl()->getHost()
Diese Ausdrücke können Sie überall verwenden, beim Erstellen von Diensten, in Argumenten, im Abschnitt setup oder bei Parametern:
parameters:
ipAddress: @http.request::getRemoteAddress()
services:
database:
create: DatabaseFactory::create( @anotherService::getDsn() )
setup:
- initialize( ::getenv('DB_USER') )
Spezielle Funktionen
In Konfigurationsdateien können Sie diese speziellen Funktionen verwenden:
not()
Negation eines Wertesbool()
,int()
,float()
,string()
verlustfreie Typumwandlung in den angegebenen Typtyped()
erstellt ein Array aller Dienste des angegebenen Typstagged()
erstellt ein Array aller Dienste mit dem angegebenen Tag
services:
- Foo(
id: int(::getenv('ProjectId'))
productionMode: not(%debugMode%)
)
Im Gegensatz zur klassischen Typumwandlung in PHP, wie z. B. (int)
, wirft die verlustfreie Typumwandlung eine
Ausnahme für nicht-numerische Werte.
Die Funktion typed()
erstellt ein Array aller Dienste des angegebenen Typs (Klasse oder Schnittstelle). Sie lässt
Dienste aus, deren Autowiring deaktiviert ist. Es können auch mehrere Typen, durch Komma getrennt, angegeben werden.
services:
- BarsDependent( typed(Bar) )
Arrays von Diensten eines bestimmten Typs können auch automatisch mittels Autowiring als Argument übergeben werden.
Die Funktion tagged()
erstellt dann ein Array aller Dienste mit einem bestimmten Tag. Auch hier können Sie
mehrere Tags durch Komma getrennt angeben.
services:
- LoggersDependent( tagged(logger) )
Autowiring
Der Schlüssel autowired
ermöglicht es, das Verhalten des Autowirings für einen bestimmten Dienst zu
beeinflussen. Für Details siehe Kapitel über
Autowiring.
services:
foo:
create: Foo
autowired: false # der Dienst foo wird vom Autowiring ausgeschlossen
Lazy Dienste
Lazy Loading ist eine Technik, die die Erstellung eines Dienstes bis zu dem Zeitpunkt aufschiebt, an dem er tatsächlich benötigt wird. In der globalen Konfiguration kann die lazy Erstellung für alle Dienste gleichzeitig aktiviert werden. Für einzelne Dienste können Sie dieses Verhalten dann überschreiben:
services:
foo:
create: Foo
lazy: false
Wenn ein Dienst als lazy definiert ist, erhalten wir bei seiner Anforderung aus dem DI-Container ein spezielles Platzhalterobjekt. Dieses sieht aus und verhält sich genauso wie der tatsächliche Dienst, aber die tatsächliche Initialisierung (Aufruf des Konstruktors und des Setups) erfolgt erst beim ersten Zugriff auf eine seiner Methoden oder Eigenschaften.
Lazy Loading kann nur für benutzerdefinierte Klassen verwendet werden, nicht für interne PHP-Klassen. Erfordert PHP 8.4 oder neuer.
Tags
Tags dienen dazu, Diensten zusätzliche Informationen hinzuzufügen. Sie können einem Dienst einen oder mehrere Tags hinzufügen:
services:
foo:
create: Foo
tags:
- cached
Tags können auch Werte tragen:
services:
foo:
create: Foo
tags:
logger: monolog.logger.event
Um alle Dienste mit bestimmten Tags zu erhalten, können Sie die Funktion tagged()
verwenden:
services:
- LoggersDependent( tagged(logger) )
Im DI-Container können Sie die Namen aller Dienste mit einem bestimmten Tag mithilfe der Methode findByTag()
abrufen:
$names = $container->findByTag('logger');
// $names ist ein Array, das den Dienstnamen und den Tag-Wert enthält
// z. B. ['foo' => 'monolog.logger.event', ...]
Inject-Modus
Mit dem Flag inject: true
wird die Übergabe von Abhängigkeiten über öffentliche Variablen mit der Annotation
inject und Methoden inject*() aktiviert.
services:
articles:
create: App\Model\Articles
inject: true
Standardmäßig ist inject
nur für Presenter aktiviert.
Modifikation von Diensten
Der DI-Container enthält viele Dienste, die über eingebaute oder benutzerdefinierte Erweiterungen hinzugefügt wurden. Sie
können die Definitionen dieser Dienste direkt in der Konfiguration ändern. Beispielsweise können Sie die Klasse des Dienstes
application.application
, die standardmäßig Nette\Application\Application
ist, in eine andere
ändern:
services:
application.application:
create: MyApplication
alteration: true
Das Flag alteration
ist informativ und besagt, dass wir nur einen bestehenden Dienst modifizieren.
Wir können auch das Setup ergänzen:
services:
application.application:
create: MyApplication
alteration: true
setup:
- '$onStartup[]' = [@resource, init]
Beim Überschreiben eines Dienstes möchten wir möglicherweise die ursprünglichen Argumente, Setup-Einträge oder Tags
entfernen, wozu reset
dient:
services:
application.application:
create: MyApplication
alteration: true
reset:
- arguments
- setup
- tags
Wenn Sie einen durch eine Erweiterung hinzugefügten Dienst entfernen möchten, können Sie dies wie folgt tun:
services:
cache.journal: false