Nette Documentation Preview

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

.[perex]
Az Autowiring egy nagyszerű funkció, amely automatikusan átadja a szükséges szolgáltatásokat a konstruktornak és más metódusoknak, így egyáltalán nem kell őket megírnunk. Rengeteg időt takarít meg Önnek.

Ennek köszönhetően a szolgáltatásdefiníciók írásakor a legtöbb argumentumot elhagyhatjuk. Helyette:

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

Elég ennyit írni:

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

Az autowiring típusok alapján működik, tehát ahhoz, hogy működjön, az `ArticleRepository` osztályt valahogy így kell definiálni:

```php
namespace Model;

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

Az autowiring használatához minden típushoz **pontosan egy szolgáltatásnak** kell lennie a konténerben. Ha több lenne belőlük, az autowiring nem tudná, melyiket adja át, és kivételt dobna:

```neon
services:
	mainDb: PDO(%dsn%, %user%, %password%)
	tempDb: PDO('sqlite::memory:')
	articles: Model\ArticleRepository  # KIVÉTELT DOB, a mainDb és a tempDb is megfelel
```

A megoldás az lenne, ha vagy megkerülnénk az autowiringot, és explicit módon megadnánk a szolgáltatás nevét (azaz `articles: Model\ArticleRepository(@mainDb)`). De ügyesebb az egyik szolgáltatás autowiringját [kikapcsolni |#Autowiring kikapcsolása], vagy az első szolgáltatást [előnyben részesíteni |#Autowiring preferencia].


Autowiring kikapcsolása
-----------------------

Egy szolgáltatás autowiringját kikapcsolhatjuk az `autowired: no` opcióval:

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

	tempDb:
		create: PDO('sqlite::memory:')
		autowired: false               # a tempDb szolgáltatás ki van zárva az autowiringból

	articles: Model\ArticleRepository  # tehát a konstruktorba a mainDb-t adja át
```

Az `articles` szolgáltatás nem dob kivételt, hogy két megfelelő `PDO` típusú szolgáltatás létezik (azaz `mainDb` és `tempDb`), amelyeket át lehet adni a konstruktorba, mert csak a `mainDb` szolgáltatást látja.

.[note]
Az autowiring konfigurációja a Nette-ben másképp működik, mint a Symfony-ban, ahol az `autowire: false` opció azt mondja, hogy ne használja az autowiringot az adott szolgáltatás konstruktorának argumentumaihoz. A Nette-ben az autowiring mindig használatos, akár a konstruktor argumentumaihoz, akár bármely más metódushoz. Az `autowired: false` opció azt mondja, hogy az adott szolgáltatás példányát ne adják át sehova autowiring segítségével.


Autowiring preferencia
----------------------

Ha több azonos típusú szolgáltatásunk van, és az egyiknél megadjuk az `autowired` opciót, ez a szolgáltatás preferálttá válik:

```neon
services:
	mainDb:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO    # preferálttá válik

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

	articles: Model\ArticleRepository
```

Az `articles` szolgáltatás nem dob kivételt, hogy két megfelelő `PDO` típusú szolgáltatás létezik (azaz `mainDb` és `tempDb`), hanem a preferált szolgáltatást használja, tehát a `mainDb`-t.


Szolgáltatások tömbje
---------------------

Az autowiring képes átadni egy adott típusú szolgáltatások tömbjét is. Mivel PHP-ban natívan nem lehet megadni a tömb elemeinek típusát, a `array` típus mellett egy phpDoc kommentet is hozzá kell adni az elem típusával `ClassName[]` formában:

```php
namespace Model;

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

A DI konténer ezután automatikusan átadja az adott típusnak megfelelő szolgáltatások tömbjét. Kihagyja azokat a szolgáltatásokat, amelyeknek ki van kapcsolva az autowiringja.

A kommentben szereplő típus lehet `array<int, Class>` vagy `list<Class>` formájú is. Ha nem tudja befolyásolni a phpDoc komment formáját, átadhatja a szolgáltatások tömbjét közvetlenül a konfigurációban a [`typed()` |services#Speciális függvények] segítségével.


Skalár argumentumok
-------------------

Az autowiring csak objektumokat és objektumok tömbjeit tudja beilleszteni. A skalár argumentumokat (pl. stringek, számok, logikai értékek) [a konfigurációban írjuk le |services#Argumentumok]. Alternatíva egy [settings-objektum |best-practices:passing-settings-to-presenters] létrehozása, amely a skalár értéket (vagy több értéket) objektum formájába csomagolja, és ezt aztán újra át lehet adni autowiring segítségével.

```php
class MySettings
{
	public function __construct(
		// a readonly PHP 8.1-től használható
		public readonly bool $value,
	)
	{}
}
```

Szolgáltatást hozhat létre belőle a konfigurációhoz való hozzáadással:

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

Ezután minden osztály autowiring segítségével kérheti azt.


Autowiring szűkítése
--------------------

Az egyes szolgáltatások autowiringját le lehet szűkíteni csak bizonyos osztályokra vagy interfészekre.

Normális esetben az autowiring átadja a szolgáltatást minden olyan metódusparaméternek, amelynek típusa megfelel a szolgáltatásnak. A szűkítés azt jelenti, hogy feltételeket szabunk, amelyeknek a metódusparamétereknél megadott típusoknak meg kell felelniük ahhoz, hogy a szolgáltatást átadják nekik.

Nézzünk egy példát:

```php
class ParentClass
{}

class ChildClass extends ParentClass
{}

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

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

Ha mindegyiket szolgáltatásként regisztrálnánk, az autowiring meghiúsulna:

```neon
services:
	parent: ParentClass
	child: ChildClass
	parentDep: ParentDependent  # KIVÉTELT DOB, a parent és a child szolgáltatás is megfelel
	childDep: ChildDependent    # az autowiring a child szolgáltatást adja át a konstruktorba
```

A `parentDep` szolgáltatás `Multiple services of type ParentClass found: parent, child` kivételt dob, mert a konstruktorába mind a `parent`, mind a `child` szolgáltatás illeszkedik, és az autowiring nem tudja eldönteni, melyiket válassza.

Ezért a `child` szolgáltatásnál leszűkíthetjük az autowiringját a `ChildClass` típusra:

```neon
services:
	parent: ParentClass
	child:
		create: ChildClass
		autowired: ChildClass   # 'autowired: self'-et is lehet írni

	parentDep: ParentDependent  # az autowiring a parent szolgáltatást adja át a konstruktorba
	childDep: ChildDependent    # az autowiring a child szolgáltatást adja át a konstruktorba
```

Most a `parentDep` szolgáltatás konstruktorába a `parent` szolgáltatás kerül átadásra, mert most ez az egyetlen megfelelő objektum. A `child` szolgáltatást az autowiring már nem adja át oda. Igen, a `child` szolgáltatás továbbra is `ParentClass` típusú, de már nem teljesül a paraméter típusára vonatkozó szűkítő feltétel, azaz nem igaz, hogy a `ParentClass` *felülírja* a `ChildClass`-t.

A `child` szolgáltatásnál az `autowired: ChildClass`-t `autowired: self`-ként is lehetne írni, mivel a `self` az aktuális szolgáltatás osztályának helyettesítő jelölése.

Az `autowired` kulcsban több osztályt vagy interfészt is meg lehet adni tömbként:

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

Próbáljuk meg a példát kiegészíteni egy interfésszel:

```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)
	{}
}
```

Ha a `child` szolgáltatást semmilyen módon nem korlátozzuk, akkor illeszkedni fog az összes `FooDependent`, `BarDependent`, `ParentDependent` és `ChildDependent` osztály konstruktorába, és az autowiring oda fogja átadni.

Ha azonban az autowiringját leszűkítjük a `ChildClass`-ra az `autowired: ChildClass` (vagy `self`) segítségével, az autowiring csak a `ChildDependent` konstruktorába adja át, mert az `ChildClass` típusú argumentumot igényel, és igaz, hogy a `ChildClass` *típusa* `ChildClass`. A többi paraméternél megadott további típusok egyike sem felülírja a `ChildClass`-t, így a szolgáltatás nem kerül átadásra.

Ha a `ParentClass`-ra korlátozzuk az `autowired: ParentClass` segítségével, az autowiring ismét átadja a `ChildDependent` konstruktorába (mert a szükséges `ChildClass` felülírja a `ParentClass`-t), és újonnan a `ParentDependent` konstruktorába is, mert a szükséges `ParentClass` típus szintén megfelelő.

Ha a `FooInterface`-re korlátozzuk, akkor továbbra is autowire-olva lesz a `ParentDependent`-be (a szükséges `ParentClass` felülírja a `FooInterface`-t) és a `ChildDependent`-be, de ráadásul a `FooDependent` konstruktorába is, viszont nem a `BarDependent`-be, mert a `BarInterface` nem felülírja a `FooInterface`-t.

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

	fooDep: FooDependent        # az autowiring a child-ot adja át a konstruktorba
	barDep: BarDependent        # KIVÉTELT DOB, egyetlen szolgáltatás sem felel meg
	parentDep: ParentDependent  # az autowiring a child-ot adja át a konstruktorba
	childDep: ChildDependent    # az autowiring a child-ot adja át a konstruktorba
```

Autowiring

Az Autowiring egy nagyszerű funkció, amely automatikusan átadja a szükséges szolgáltatásokat a konstruktornak és más metódusoknak, így egyáltalán nem kell őket megírnunk. Rengeteg időt takarít meg Önnek.

Ennek köszönhetően a szolgáltatásdefiníciók írásakor a legtöbb argumentumot elhagyhatjuk. Helyette:

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

Elég ennyit írni:

services:
	articles: Model\ArticleRepository

Az autowiring típusok alapján működik, tehát ahhoz, hogy működjön, az ArticleRepository osztályt valahogy így kell definiálni:

namespace Model;

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

Az autowiring használatához minden típushoz pontosan egy szolgáltatásnak kell lennie a konténerben. Ha több lenne belőlük, az autowiring nem tudná, melyiket adja át, és kivételt dobna:

services:
	mainDb: PDO(%dsn%, %user%, %password%)
	tempDb: PDO('sqlite::memory:')
	articles: Model\ArticleRepository  # KIVÉTELT DOB, a mainDb és a tempDb is megfelel

A megoldás az lenne, ha vagy megkerülnénk az autowiringot, és explicit módon megadnánk a szolgáltatás nevét (azaz articles: Model\ArticleRepository(@mainDb)). De ügyesebb az egyik szolgáltatás autowiringját kikapcsolni, vagy az első szolgáltatást előnyben részesíteni.

Autowiring kikapcsolása

Egy szolgáltatás autowiringját kikapcsolhatjuk az autowired: no opcióval:

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

	tempDb:
		create: PDO('sqlite::memory:')
		autowired: false               # a tempDb szolgáltatás ki van zárva az autowiringból

	articles: Model\ArticleRepository  # tehát a konstruktorba a mainDb-t adja át

Az articles szolgáltatás nem dob kivételt, hogy két megfelelő PDO típusú szolgáltatás létezik (azaz mainDb és tempDb), amelyeket át lehet adni a konstruktorba, mert csak a mainDb szolgáltatást látja.

Az autowiring konfigurációja a Nette-ben másképp működik, mint a Symfony-ban, ahol az autowire: false opció azt mondja, hogy ne használja az autowiringot az adott szolgáltatás konstruktorának argumentumaihoz. A Nette-ben az autowiring mindig használatos, akár a konstruktor argumentumaihoz, akár bármely más metódushoz. Az autowired: false opció azt mondja, hogy az adott szolgáltatás példányát ne adják át sehova autowiring segítségével.

Autowiring preferencia

Ha több azonos típusú szolgáltatásunk van, és az egyiknél megadjuk az autowired opciót, ez a szolgáltatás preferálttá válik:

services:
	mainDb:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO    # preferálttá válik

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

	articles: Model\ArticleRepository

Az articles szolgáltatás nem dob kivételt, hogy két megfelelő PDO típusú szolgáltatás létezik (azaz mainDb és tempDb), hanem a preferált szolgáltatást használja, tehát a mainDb-t.

Szolgáltatások tömbje

Az autowiring képes átadni egy adott típusú szolgáltatások tömbjét is. Mivel PHP-ban natívan nem lehet megadni a tömb elemeinek típusát, a array típus mellett egy phpDoc kommentet is hozzá kell adni az elem típusával ClassName[] formában:

namespace Model;

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

A DI konténer ezután automatikusan átadja az adott típusnak megfelelő szolgáltatások tömbjét. Kihagyja azokat a szolgáltatásokat, amelyeknek ki van kapcsolva az autowiringja.

A kommentben szereplő típus lehet array<int, Class> vagy list<Class> formájú is. Ha nem tudja befolyásolni a phpDoc komment formáját, átadhatja a szolgáltatások tömbjét közvetlenül a konfigurációban a typed() segítségével.

Skalár argumentumok

Az autowiring csak objektumokat és objektumok tömbjeit tudja beilleszteni. A skalár argumentumokat (pl. stringek, számok, logikai értékek) a konfigurációban írjuk le. Alternatíva egy settings-objektum létrehozása, amely a skalár értéket (vagy több értéket) objektum formájába csomagolja, és ezt aztán újra át lehet adni autowiring segítségével.

class MySettings
{
	public function __construct(
		// a readonly PHP 8.1-től használható
		public readonly bool $value,
	)
	{}
}

Szolgáltatást hozhat létre belőle a konfigurációhoz való hozzáadással:

services:
	- MySettings('any value')

Ezután minden osztály autowiring segítségével kérheti azt.

Autowiring szűkítése

Az egyes szolgáltatások autowiringját le lehet szűkíteni csak bizonyos osztályokra vagy interfészekre.

Normális esetben az autowiring átadja a szolgáltatást minden olyan metódusparaméternek, amelynek típusa megfelel a szolgáltatásnak. A szűkítés azt jelenti, hogy feltételeket szabunk, amelyeknek a metódusparamétereknél megadott típusoknak meg kell felelniük ahhoz, hogy a szolgáltatást átadják nekik.

Nézzünk egy példát:

class ParentClass
{}

class ChildClass extends ParentClass
{}

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

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

Ha mindegyiket szolgáltatásként regisztrálnánk, az autowiring meghiúsulna:

services:
	parent: ParentClass
	child: ChildClass
	parentDep: ParentDependent  # KIVÉTELT DOB, a parent és a child szolgáltatás is megfelel
	childDep: ChildDependent    # az autowiring a child szolgáltatást adja át a konstruktorba

A parentDep szolgáltatás Multiple services of type ParentClass found: parent, child kivételt dob, mert a konstruktorába mind a parent, mind a child szolgáltatás illeszkedik, és az autowiring nem tudja eldönteni, melyiket válassza.

Ezért a child szolgáltatásnál leszűkíthetjük az autowiringját a ChildClass típusra:

services:
	parent: ParentClass
	child:
		create: ChildClass
		autowired: ChildClass   # 'autowired: self'-et is lehet írni

	parentDep: ParentDependent  # az autowiring a parent szolgáltatást adja át a konstruktorba
	childDep: ChildDependent    # az autowiring a child szolgáltatást adja át a konstruktorba

Most a parentDep szolgáltatás konstruktorába a parent szolgáltatás kerül átadásra, mert most ez az egyetlen megfelelő objektum. A child szolgáltatást az autowiring már nem adja át oda. Igen, a child szolgáltatás továbbra is ParentClass típusú, de már nem teljesül a paraméter típusára vonatkozó szűkítő feltétel, azaz nem igaz, hogy a ParentClass felülírja a ChildClass-t.

A child szolgáltatásnál az autowired: ChildClass-t autowired: self-ként is lehetne írni, mivel a self az aktuális szolgáltatás osztályának helyettesítő jelölése.

Az autowired kulcsban több osztályt vagy interfészt is meg lehet adni tömbként:

autowired: [BarClass, FooInterface]

Próbáljuk meg a példát kiegészíteni egy interfésszel:

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)
	{}
}

Ha a child szolgáltatást semmilyen módon nem korlátozzuk, akkor illeszkedni fog az összes FooDependent, BarDependent, ParentDependent és ChildDependent osztály konstruktorába, és az autowiring oda fogja átadni.

Ha azonban az autowiringját leszűkítjük a ChildClass-ra az autowired: ChildClass (vagy self) segítségével, az autowiring csak a ChildDependent konstruktorába adja át, mert az ChildClass típusú argumentumot igényel, és igaz, hogy a ChildClass típusa ChildClass. A többi paraméternél megadott további típusok egyike sem felülírja a ChildClass-t, így a szolgáltatás nem kerül átadásra.

Ha a ParentClass-ra korlátozzuk az autowired: ParentClass segítségével, az autowiring ismét átadja a ChildDependent konstruktorába (mert a szükséges ChildClass felülírja a ParentClass-t), és újonnan a ParentDependent konstruktorába is, mert a szükséges ParentClass típus szintén megfelelő.

Ha a FooInterface-re korlátozzuk, akkor továbbra is autowire-olva lesz a ParentDependent-be (a szükséges ParentClass felülírja a FooInterface-t) és a ChildDependent-be, de ráadásul a FooDependent konstruktorába is, viszont nem a BarDependent-be, mert a BarInterface nem felülírja a FooInterface-t.

services:
	child:
		create: ChildClass
		autowired: FooInterface

	fooDep: FooDependent        # az autowiring a child-ot adja át a konstruktorba
	barDep: BarDependent        # KIVÉTELT DOB, egyetlen szolgáltatás sem felel meg
	parentDep: ParentDependent  # az autowiring a child-ot adja át a konstruktorba
	childDep: ChildDependent    # az autowiring a child-ot adja át a konstruktorba