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 Wertes
  • bool(), int(), float(), string() verlustfreie Typumwandlung in den angegebenen Typ
  • typed() erstellt ein Array aller Dienste des angegebenen Typs
  • tagged() 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
Version: 3.x