Nette Documentation Preview

syntax
Autowiring
**********

.[perex]
Autowiring ist eine großartige Fähigkeit, die dem Konstruktor und anderen Methoden automatisch die benötigten Services übergibt, sodass wir sie nicht ausdrücklich angeben müssen. Das spart Ihnen viel Zeit.

Dadurch können wir beim Schreiben von Service-Definitionen die allermeisten Argumente weglassen. Statt:

```neon
services:
	articles: Model\ArticleRepository(@database, @cache.storage)
```

schreiben Sie einfach:

```neon
services:
	articles: Model\ArticleRepository
```

Das Autowiring richtet sich nach Typen, damit es funktioniert, muss die Klasse `ArticleRepository` also ungefähr so definiert sein:

```php
namespace Model;

class ArticleRepository
{
	public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
	{}
}
```

Autowiring verwendet niemals die Namen von Services. Es richtet sich einzig nach dem Typsystem von PHP, weiß also auch, dass eine Klasse die Interfaces erfüllt, die sie implementiert, und die Klassen, von denen sie erbt. Dadurch ist der Name eines Services bloß ein Hilfsbezeichner, und ihn umzubenennen zerstört in der Anwendung nichts.

Damit sich Autowiring nutzen lässt, muss es im Container von jedem Typ **genau einen Service** geben. Gäbe es mehrere, wüsste das Autowiring nicht, welchen es übergeben soll, und würde eine Exception werfen:

```neon
services:
	mainDb: PDO(%dsn%, %user%, %password%)
	tempDb: PDO('sqlite::memory:')
	articles: Model\ArticleRepository  # WIRFT EINE EXCEPTION, sowohl mainDb als auch tempDb passen
```

Eine Lösung ist, das Autowiring zu umgehen und den Namen des Services ausdrücklich anzugeben (etwa `articles: Model\ArticleRepository(@mainDb)`). Bequemer ist es jedoch, das Autowiring für einen der Services [abzuschalten |#Autowiring abschalten] oder einen Service gegenüber den anderen zu [bevorzugen |#Bevorzugung beim Autowiring].


Autowiring abschalten
---------------------

Über die Option `autowired: false` können wir das Autowiring für einen Service abschalten:

```neon
services:
	mainDb: PDO(%dsn%, %user%, %password%)

	tempDb:
		create: PDO('sqlite::memory:')
		autowired: false               # der Service tempDb ist vom Autowiring ausgenommen

	articles: Model\ArticleRepository  # dem Konstruktor wird daher mainDb übergeben
```

Der Service `articles` wirft keine Exception darüber, dass für den Konstruktor zwei passende `PDO`-Services (`mainDb` und `tempDb`) zur Verfügung stehen, denn er zieht nur den Service `mainDb` in Betracht.

Autowiring lässt sich über die Konfigurationsoption [`di › excluded` |configuration#DI] auch global für ganze Typen abschalten; dort werden die Typen (und ihre Nachfahren) aufgeführt, die nie autowiret werden sollen.

.[note]
Die Konfiguration des Autowirings unterscheidet sich in Nette von der in Symfony. In Symfony bedeutet `autowire: false`, dass für die Konstruktorargumente des Services kein Autowiring verwendet werden soll. In Nette betrifft das Autowiring die Konstruktorargumente und alle weiteren Methoden, die über den Container aufgerufen werden (etwa Setter Injection). Die Option `autowired: false` verhindert, dass der Container diese Instanz des Services automatisch als Abhängigkeit an andere Services übergibt.


Bevorzugung beim Autowiring
---------------------------

Haben wir mehrere Services desselben Typs und geben für einen von ihnen die Option `autowired` an, wird dieser Service zum bevorzugten:

```neon
services:
	mainDb:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO    # wird zum bevorzugten

	tempDb:
		create: PDO('sqlite::memory:')

	articles: Model\ArticleRepository
```

Der Service `articles` wirft keine Exception darüber, dass mehrere `PDO`-Services passen (`mainDb` und `tempDb`), sondern verwendet den bevorzugten, also `mainDb`.


Sammlung von Services
---------------------

Autowiring kann auch Arrays von Services eines bestimmten Typs übergeben. Weil PHP von Haus aus nicht erlaubt, den Typ der Array-Elemente in einer Typdeklaration anzugeben, müssen Sie die Typdeklaration `array` um einen phpDoc-Kommentar mit dem Typ der Elemente ergänzen, etwa `ClassName[]`:

```php
namespace Model;

class ShipManager
{
	/**
	 * @param Shipper[] $shippers
	 */
	public function __construct(array $shippers)
	{}
}
```

Der DI-Container übergibt dann automatisch ein Array der Services, die dem angegebenen Typ entsprechen. Services mit [abgeschaltetem Autowiring |#Autowiring abschalten] lässt er aus, und den gerade erzeugten Service nimmt er nie in dessen eigene Sammlung auf. Anders als beim Übergeben eines einzelnen Services haben das [Einschränken |#Autowiring einschränken] des Autowirings auf einen bestimmten Typ und das Kennzeichnen eines Services als [bevorzugt |#Bevorzugung beim Autowiring] hier keine Wirkung - das Array enthält immer alle Services des angegebenen Typs.

Der Typ im Kommentar kann auch die Form `array<int, Class>` oder `list<Class>` haben. Wenn Sie die Form des phpDoc-Kommentars nicht beeinflussen können, lässt sich ein Array von Services über [`typed()` |services#Spezielle Funktionen] direkt in der Konfiguration übergeben.


Skalare Argumente
-----------------

Autowiring funktioniert nur für Objekte und Arrays von Objekten. Skalare Argumente (etwa Strings, Zahlen, boolesche Werte) müssen [in der Konfiguration angegeben |services#Argumente] werden. Eine Alternative ist, ein [Objekt mit Einstellungen|best-practices:passing-settings-to-presenters] zu erzeugen, das den skalaren Wert (oder mehrere Werte) kapselt. Dieses Objekt lässt sich dann über Autowiring übergeben.

```php
class MySettings
{
	public function __construct(
		// readonly lässt sich seit PHP 8.1 verwenden
		public readonly bool $value,
	)
	{}
}
```

Als Service registrieren Sie es, indem Sie es der Konfiguration hinzufügen:

```neon
services:
	- MySettings('any value')
```

Andere Klassen können es dann über Autowiring anfordern.


Optionale Abhängigkeiten
------------------------

Hat ein Parameter des Konstruktors oder einer Methode einen Standardwert und existiert im Container kein Service des verlangten Typs, wirft das Autowiring keine Exception - es überspringt das Argument einfach, sodass der Standardwert verwendet wird. So deklarieren Sie optionale Abhängigkeiten:

```php
class Foo
{
	public function __construct(
		private ?Logger $logger = null,
	) {}
}
```

Bei einem Parameter ohne Standardwert führt ein fehlender Service dagegen immer zu einer Exception.


Autowiring einschränken
-----------------------

Für einzelne Services lässt sich das Autowiring auf bestimmte Klassen oder Interfaces einschränken.

Normalerweise übergibt das Autowiring einen Service an jeden Methodenparameter, dessen Typ zum Service passt. Einschränken bedeutet, dass wir Bedingungen aufstellen, die die für die Methodenparameter angegebenen Typen erfüllen müssen, damit ihnen der Service übergeben wird.

Nehmen wir ein Beispiel:

```php
class ParentClass
{}

class ChildClass extends ParentClass
{}

class ParentDependent
{
	function __construct(ParentClass $obj)
	{}
}

class ChildDependent
{
	function __construct(ChildClass $obj)
	{}
}
```

Würden wir sie alle als Services registrieren, scheiterte das Autowiring:

```neon
services:
	parent: ParentClass
	child: ChildClass
	parentDep: ParentDependent  # WIRFT EINE EXCEPTION, sowohl parent als auch child passen
	childDep: ChildDependent    # das Autowiring übergibt dem Konstruktor den Service child
```

Der Service `parentDep` wirft die Exception `Multiple services of type ParentClass found: child, parent`, weil sowohl der Service `parent` als auch `child` in seinen Konstruktor passen und das Autowiring sich nicht entscheiden kann, welchen es wählen soll.

Für den Service `child` können wir das Autowiring deshalb auf den Typ `ChildClass` einschränken:

```neon
services:
	parent: ParentClass
	child:
		create: ChildClass
		autowired: ChildClass   # lässt sich auch als 'autowired: self' schreiben

	parentDep: ParentDependent  # das Autowiring übergibt dem Konstruktor den Service parent
	childDep: ChildDependent    # das Autowiring übergibt dem Konstruktor den Service child
```

Jetzt wird dem Konstruktor des Services `parentDep` der Service `parent` übergeben, denn er ist nun das einzige passende Objekt. Der Service `child` wird vom Autowiring nicht mehr dorthin übergeben. Ja, der Service `child` ist weiterhin vom Typ `ParentClass`, aber die Einschränkung `autowired: ChildClass` bewirkt, dass er nur an Parameter übergeben wird, die ausdrücklich als `ChildClass` (oder als deren Untertypen) deklariert sind. Weil `ParentDependent` ein `ParentClass` verlangt, kommt der Service `child` dort nicht mehr als Kandidat für das Autowiring in Betracht.

Für den Service `child` ließe sich `autowired: ChildClass` auch als `autowired: self` schreiben, denn `self` ist ein Platzhalter für die Klasse des aktuellen Services.

Im Schlüssel `autowired` lassen sich auch mehrere Klassen oder Interfaces als Array angeben:

```neon
autowired: [ParentClass, FooInterface]
```

Versuchen wir, dem Beispiel Interfaces hinzuzufügen:

```php
interface FooInterface
{}

interface BarInterface
{}

class ParentClass implements FooInterface
{}

class ChildClass extends ParentClass implements BarInterface
{}

class FooDependent
{
	function __construct(FooInterface $obj)
	{}
}

class BarDependent
{
	function __construct(BarInterface $obj)
	{}
}

class ParentDependent
{
	function __construct(ParentClass $obj)
	{}
}

class ChildDependent
{
	function __construct(ChildClass $obj)
	{}
}
```

Schränken wir den Service `child` in keiner Weise ein, passt er in die Konstruktoren aller Klassen `FooDependent`, `BarDependent`, `ParentDependent` und `ChildDependent`, und das Autowiring übergibt ihn dorthin.

Schränken wir sein Autowiring jedoch mit `autowired: ChildClass` (oder `self`) auf `ChildClass` ein, übergibt es ihn nur dem Konstruktor von `ChildDependent`, weil dieser ein Argument vom Typ `ChildClass` verlangt und gilt, dass `ChildClass` *vom Typ* `ChildClass` ist. Bei keinem der anderen Parameter ist der verlangte Typ `ChildClass` oder ein Untertyp davon, der Service wird ihnen also nicht übergeben.

Schränken wir ihn mit `autowired: ParentClass` auf `ParentClass` ein, übergibt ihn das Autowiring wieder dem Konstruktor von `ChildDependent` (weil die verlangte `ChildClass` ein Untertyp von `ParentClass` ist) und jetzt auch dem Konstruktor von `ParentDependent`, denn der verlangte Typ `ParentClass` passt ebenfalls.

Schränken wir ihn auf `FooInterface` ein, wird er weiterhin in `ParentDependent` (die verlangte `ParentClass` ist ein Untertyp von `FooInterface`) und in `ChildDependent` autowiret, zusätzlich auch in den Konstruktor von `FooDependent`, nicht aber in `BarDependent`, weil `BarInterface` kein Untertyp von `FooInterface` ist.

```neon
services:
	child:
		create: ChildClass
		autowired: FooInterface

	fooDep: FooDependent        # das Autowiring übergibt dem Konstruktor den Service child
	barDep: BarDependent        # WIRFT EINE EXCEPTION, kein Service passt
	parentDep: ParentDependent  # das Autowiring übergibt dem Konstruktor den Service child
	childDep: ChildDependent    # das Autowiring übergibt dem Konstruktor den Service child
```

Autowiring

Autowiring ist eine großartige Fähigkeit, die dem Konstruktor und anderen Methoden automatisch die benötigten Services übergibt, sodass wir sie nicht ausdrücklich angeben müssen. Das spart Ihnen viel Zeit.

Dadurch können wir beim Schreiben von Service-Definitionen die allermeisten Argumente weglassen. Statt:

services:
	articles: Model\ArticleRepository(@database, @cache.storage)

schreiben Sie einfach:

services:
	articles: Model\ArticleRepository

Das Autowiring richtet sich nach Typen, damit es funktioniert, muss die Klasse ArticleRepository also ungefähr so definiert sein:

namespace Model;

class ArticleRepository
{
	public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
	{}
}

Autowiring verwendet niemals die Namen von Services. Es richtet sich einzig nach dem Typsystem von PHP, weiß also auch, dass eine Klasse die Interfaces erfüllt, die sie implementiert, und die Klassen, von denen sie erbt. Dadurch ist der Name eines Services bloß ein Hilfsbezeichner, und ihn umzubenennen zerstört in der Anwendung nichts.

Damit sich Autowiring nutzen lässt, muss es im Container von jedem Typ genau einen Service geben. Gäbe es mehrere, wüsste das Autowiring nicht, welchen es übergeben soll, und würde eine Exception werfen:

services:
	mainDb: PDO(%dsn%, %user%, %password%)
	tempDb: PDO('sqlite::memory:')
	articles: Model\ArticleRepository  # WIRFT EINE EXCEPTION, sowohl mainDb als auch tempDb passen

Eine Lösung ist, das Autowiring zu umgehen und den Namen des Services ausdrücklich anzugeben (etwa articles: Model\ArticleRepository(@mainDb)). Bequemer ist es jedoch, das Autowiring für einen der Services abzuschalten oder einen Service gegenüber den anderen zu bevorzugen.

Autowiring abschalten

Über die Option autowired: false können wir das Autowiring für einen Service abschalten:

services:
	mainDb: PDO(%dsn%, %user%, %password%)

	tempDb:
		create: PDO('sqlite::memory:')
		autowired: false               # der Service tempDb ist vom Autowiring ausgenommen

	articles: Model\ArticleRepository  # dem Konstruktor wird daher mainDb übergeben

Der Service articles wirft keine Exception darüber, dass für den Konstruktor zwei passende PDO-Services (mainDb und tempDb) zur Verfügung stehen, denn er zieht nur den Service mainDb in Betracht.

Autowiring lässt sich über die Konfigurationsoption di › excluded auch global für ganze Typen abschalten; dort werden die Typen (und ihre Nachfahren) aufgeführt, die nie autowiret werden sollen.

Die Konfiguration des Autowirings unterscheidet sich in Nette von der in Symfony. In Symfony bedeutet autowire: false, dass für die Konstruktorargumente des Services kein Autowiring verwendet werden soll. In Nette betrifft das Autowiring die Konstruktorargumente und alle weiteren Methoden, die über den Container aufgerufen werden (etwa Setter Injection). Die Option autowired: false verhindert, dass der Container diese Instanz des Services automatisch als Abhängigkeit an andere Services übergibt.

Bevorzugung beim Autowiring

Haben wir mehrere Services desselben Typs und geben für einen von ihnen die Option autowired an, wird dieser Service zum bevorzugten:

services:
	mainDb:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO    # wird zum bevorzugten

	tempDb:
		create: PDO('sqlite::memory:')

	articles: Model\ArticleRepository

Der Service articles wirft keine Exception darüber, dass mehrere PDO-Services passen (mainDb und tempDb), sondern verwendet den bevorzugten, also mainDb.

Sammlung von Services

Autowiring kann auch Arrays von Services eines bestimmten Typs übergeben. Weil PHP von Haus aus nicht erlaubt, den Typ der Array-Elemente in einer Typdeklaration anzugeben, müssen Sie die Typdeklaration array um einen phpDoc-Kommentar mit dem Typ der Elemente ergänzen, etwa ClassName[]:

namespace Model;

class ShipManager
{
	/**
	 * @param Shipper[] $shippers
	 */
	public function __construct(array $shippers)
	{}
}

Der DI-Container übergibt dann automatisch ein Array der Services, die dem angegebenen Typ entsprechen. Services mit abgeschaltetem Autowiring lässt er aus, und den gerade erzeugten Service nimmt er nie in dessen eigene Sammlung auf. Anders als beim Übergeben eines einzelnen Services haben das Einschränken des Autowirings auf einen bestimmten Typ und das Kennzeichnen eines Services als bevorzugt hier keine Wirkung – das Array enthält immer alle Services des angegebenen Typs.

Der Typ im Kommentar kann auch die Form array<int, Class> oder list<Class> haben. Wenn Sie die Form des phpDoc-Kommentars nicht beeinflussen können, lässt sich ein Array von Services über typed() direkt in der Konfiguration übergeben.

Skalare Argumente

Autowiring funktioniert nur für Objekte und Arrays von Objekten. Skalare Argumente (etwa Strings, Zahlen, boolesche Werte) müssen in der Konfiguration angegeben werden. Eine Alternative ist, ein Objekt mit Einstellungen zu erzeugen, das den skalaren Wert (oder mehrere Werte) kapselt. Dieses Objekt lässt sich dann über Autowiring übergeben.

class MySettings
{
	public function __construct(
		// readonly lässt sich seit PHP 8.1 verwenden
		public readonly bool $value,
	)
	{}
}

Als Service registrieren Sie es, indem Sie es der Konfiguration hinzufügen:

services:
	- MySettings('any value')

Andere Klassen können es dann über Autowiring anfordern.

Optionale Abhängigkeiten

Hat ein Parameter des Konstruktors oder einer Methode einen Standardwert und existiert im Container kein Service des verlangten Typs, wirft das Autowiring keine Exception – es überspringt das Argument einfach, sodass der Standardwert verwendet wird. So deklarieren Sie optionale Abhängigkeiten:

class Foo
{
	public function __construct(
		private ?Logger $logger = null,
	) {}
}

Bei einem Parameter ohne Standardwert führt ein fehlender Service dagegen immer zu einer Exception.

Autowiring einschränken

Für einzelne Services lässt sich das Autowiring auf bestimmte Klassen oder Interfaces einschränken.

Normalerweise übergibt das Autowiring einen Service an jeden Methodenparameter, dessen Typ zum Service passt. Einschränken bedeutet, dass wir Bedingungen aufstellen, die die für die Methodenparameter angegebenen Typen erfüllen müssen, damit ihnen der Service übergeben wird.

Nehmen wir ein Beispiel:

class ParentClass
{}

class ChildClass extends ParentClass
{}

class ParentDependent
{
	function __construct(ParentClass $obj)
	{}
}

class ChildDependent
{
	function __construct(ChildClass $obj)
	{}
}

Würden wir sie alle als Services registrieren, scheiterte das Autowiring:

services:
	parent: ParentClass
	child: ChildClass
	parentDep: ParentDependent  # WIRFT EINE EXCEPTION, sowohl parent als auch child passen
	childDep: ChildDependent    # das Autowiring übergibt dem Konstruktor den Service child

Der Service parentDep wirft die Exception Multiple services of type ParentClass found: child, parent, weil sowohl der Service parent als auch child in seinen Konstruktor passen und das Autowiring sich nicht entscheiden kann, welchen es wählen soll.

Für den Service child können wir das Autowiring deshalb auf den Typ ChildClass einschränken:

services:
	parent: ParentClass
	child:
		create: ChildClass
		autowired: ChildClass   # lässt sich auch als 'autowired: self' schreiben

	parentDep: ParentDependent  # das Autowiring übergibt dem Konstruktor den Service parent
	childDep: ChildDependent    # das Autowiring übergibt dem Konstruktor den Service child

Jetzt wird dem Konstruktor des Services parentDep der Service parent übergeben, denn er ist nun das einzige passende Objekt. Der Service child wird vom Autowiring nicht mehr dorthin übergeben. Ja, der Service child ist weiterhin vom Typ ParentClass, aber die Einschränkung autowired: ChildClass bewirkt, dass er nur an Parameter übergeben wird, die ausdrücklich als ChildClass (oder als deren Untertypen) deklariert sind. Weil ParentDependent ein ParentClass verlangt, kommt der Service child dort nicht mehr als Kandidat für das Autowiring in Betracht.

Für den Service child ließe sich autowired: ChildClass auch als autowired: self schreiben, denn self ist ein Platzhalter für die Klasse des aktuellen Services.

Im Schlüssel autowired lassen sich auch mehrere Klassen oder Interfaces als Array angeben:

autowired: [ParentClass, FooInterface]

Versuchen wir, dem Beispiel Interfaces hinzuzufügen:

interface FooInterface
{}

interface BarInterface
{}

class ParentClass implements FooInterface
{}

class ChildClass extends ParentClass implements BarInterface
{}

class FooDependent
{
	function __construct(FooInterface $obj)
	{}
}

class BarDependent
{
	function __construct(BarInterface $obj)
	{}
}

class ParentDependent
{
	function __construct(ParentClass $obj)
	{}
}

class ChildDependent
{
	function __construct(ChildClass $obj)
	{}
}

Schränken wir den Service child in keiner Weise ein, passt er in die Konstruktoren aller Klassen FooDependent, BarDependent, ParentDependent und ChildDependent, und das Autowiring übergibt ihn dorthin.

Schränken wir sein Autowiring jedoch mit autowired: ChildClass (oder self) auf ChildClass ein, übergibt es ihn nur dem Konstruktor von ChildDependent, weil dieser ein Argument vom Typ ChildClass verlangt und gilt, dass ChildClass vom Typ ChildClass ist. Bei keinem der anderen Parameter ist der verlangte Typ ChildClass oder ein Untertyp davon, der Service wird ihnen also nicht übergeben.

Schränken wir ihn mit autowired: ParentClass auf ParentClass ein, übergibt ihn das Autowiring wieder dem Konstruktor von ChildDependent (weil die verlangte ChildClass ein Untertyp von ParentClass ist) und jetzt auch dem Konstruktor von ParentDependent, denn der verlangte Typ ParentClass passt ebenfalls.

Schränken wir ihn auf FooInterface ein, wird er weiterhin in ParentDependent (die verlangte ParentClass ist ein Untertyp von FooInterface) und in ChildDependent autowiret, zusätzlich auch in den Konstruktor von FooDependent, nicht aber in BarDependent, weil BarInterface kein Untertyp von FooInterface ist.

services:
	child:
		create: ChildClass
		autowired: FooInterface

	fooDep: FooDependent        # das Autowiring übergibt dem Konstruktor den Service child
	barDep: BarDependent        # WIRFT EINE EXCEPTION, kein Service passt
	parentDep: ParentDependent  # das Autowiring übergibt dem Konstruktor den Service child
	childDep: ChildDependent    # das Autowiring übergibt dem Konstruktor den Service child