Cablaggio auto

L'autowiring è un'ottima funzione che permette di passare automaticamente i servizi al costruttore e ad altri metodi, senza doverli scrivere. Permette di risparmiare molto tempo.

Questo ci permette di saltare la maggior parte degli argomenti quando scriviamo le definizioni dei servizi. Invece di:

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

Scrivete:

services:
	articles: Model\ArticleRepository

Il cablaggio automatico è guidato dai tipi, quindi la classe ArticleRepository deve essere definita come segue:

namespace Model;

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

Per usare l'autowiring, deve esserci un solo servizio per ogni tipo nel contenitore. Se ce ne fossero di più, l'autowiring non saprebbe quale passare e lancerebbe un'eccezione:

services:
	mainDb: PDO(%dsn%, %user%, %password%)
	tempDb: PDO('sqlite::memory:')
	articles: Model\ArticleRepository # LANCIA L'ECCEZIONE, sia mainDb che tempDb corrispondono

La soluzione sarebbe quella di bypassare l'autowiring e dichiarare esplicitamente il nome del servizio (per esempio articles: Model\ArticleRepository(@mainDb)). Tuttavia, è più conveniente disabilitare l' autowiring di un solo servizio, o del primo servizio preferito.

Cablaggio automatico disabilitato

È possibile disabilitare il cablaggio automatico del servizio utilizzando l'opzione autowired: no:

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

	tempDb:
		create: PDO('sqlite::memory:')
		autowired: false # rimuove tempDb dall'autowiring

	articles: Model\ArticleRepository    # quindi passa mainDb al costruttore

Il servizio articles non lancia l'eccezione che ci sono due servizi corrispondenti di tipo PDO (cioè mainDb e tempDb) che possono essere passati al costruttore, perché vede solo il servizio mainDb.

La configurazione dell'autowiring in Nette funziona in modo diverso rispetto a Symfony, dove l'opzione autowire: false dice che l'autowiring non deve essere usato per i parametri del costruttore di servizi. In Nette, l'autowiring è sempre usato, sia per i parametri del costruttore che per qualsiasi altro metodo. L'opzione autowired: false dice che l'istanza del servizio non deve essere passata da nessuna parte usando l'autowiring.

Autowiring preferito

Se abbiamo più servizi dello stesso tipo e uno di essi ha l'opzione autowired, questo servizio diventa quello preferito:

services:
	mainDb:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO # lo rende preferibile

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

	articles: Model\ArticleRepository

Il servizio articles non lancia l'eccezione che ci sono due servizi PDO corrispondenti (cioè mainDb e tempDb), ma utilizza il servizio preferito, cioè mainDb.

Raccolta di servizi

L'autowiring può anche passare un array di servizi di un tipo particolare. Poiché PHP non è in grado di annotare nativamente il tipo di elementi dell'array, oltre al tipo array, è necessario aggiungere un commento phpDoc con il tipo di elemento come ClassName[]:

namespace Model;

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

Il contenitore DI passa automaticamente un array di servizi che corrispondono al tipo dato. Ometterà i servizi che hanno il cablaggio automatico disattivato.

Il tipo nel commento può anche essere della forma array<int, Class> oppure list<Class>. Se non si può controllare la forma del commento di phpDoc, si può passare un array di servizi direttamente nella configurazione usando typed().

Argomenti scalari

Il cablaggio automatico può passare solo oggetti e array di oggetti. Gli argomenti scalari (ad esempio stringhe, numeri, booleani) si scrivono nella configurazione. Un'alternativa è quella di creare un oggetto settings che incapsuli un valore scalare (o più valori) come un oggetto, che può essere passato di nuovo con l'autowiring.

class MySettings
{
	public function __construct(
		// readonly può essere usato da PHP 8.1
		public readonly bool $value,
	)
	{}
}

Si crea un servizio aggiungendolo alla configurazione:

services:
	- MySettings('any value')

Tutte le classi lo richiederanno quindi tramite il cablaggio automatico.

Restringimento del cablaggio automatico

Per i singoli servizi, il cablaggio automatico può essere ristretto a classi o interfacce specifiche.

Normalmente, l'autowiring passa il servizio a ogni parametro del metodo al cui tipo il servizio corrisponde. Restringere significa specificare le condizioni che i tipi specificati per i parametri del metodo devono soddisfare affinché il servizio venga passato ad essi.

Facciamo un esempio:

class ParentClass
{}

class ChildClass extends ParentClass
{}

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

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

Se li registrassimo tutti come servizi, il cablaggio automatico fallirebbe:

services:
	parent: ParentClass
	child: ChildClass
	parentDep: ParentDependent  # LANCIA L'ECCEZIONE, sia il genitore che il figlio corrispondono
	childDep: ChildDependent    # passa il servizio 'child' al costruttore

Il servizio parentDep lancia l'eccezione Multiple services of type ParentClass found: parent, child perché sia parent che child si inseriscono nel suo costruttore e l'autowiring non può decidere quale scegliere.

Per il servizio child, possiamo quindi restringere il suo autowiring a ChildClass:

services:
	parent: ParentClass
	child:
		create: ChildClass
		autowired: ChildClass  # alternative: 'autowired: self'

	parentDep: ParentDependent # Lancia un'eccezione, il 'figlio' non può essere autowired
	childDep: ChildDependent   # passa il servizio 'child' al costruttore

Il servizio parentDep viene ora passato al costruttore del servizio parentDep, poiché ora è l'unico oggetto corrispondente. Il servizio child non viene più passato per autocablaggio. Sì, il servizio child è ancora di tipo ParentClass, ma la condizione di restringimento data per il tipo di parametro non si applica più, cioè non è più vero che ParentClass è un supertipo di ChildClass.

Nel caso di child, autowired: ChildClass potrebbe essere scritto come autowired: self, poiché self indica il tipo di servizio corrente.

La chiave autowired può includere diverse classi e interfacce come array:

autowired: [BarClass, FooInterface]

Proviamo ad aggiungere le interfacce all'esempio:

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

Se non limitiamo il servizio child, esso entrerà nei costruttori di tutte le classi FooDependent, BarDependent, ParentDependent e ChildDependent e l'autowiring lo passerà lì.

Tuttavia, se restringiamo il suo autowiring a ChildClass usando autowired: ChildClass (o self), l'autowiring lo passa solo al costruttore ChildDependent, perché richiede un argomento di tipo ChildClass e ChildClass è di tipo ChildClass. Nessun altro tipo specificato per gli altri parametri è un superset di ChildClass, quindi il servizio non viene passato.

Se lo limitiamo a ParentClass usando autowired: ParentClass, l'autowiring lo passerà di nuovo al costruttore ChildDependent (poiché il tipo richiesto ChildClass è un sottoinsieme di ParentClass) e anche al costruttore ParentDependent, poiché anche il tipo richiesto di ParentClass corrisponde.

Se lo limitiamo a FooInterface, si autocablerà ancora a ParentDependent (il tipo richiesto ParentClass è un supertipo di FooInterface) e a ChildDependent, ma anche al costruttore FooDependent, ma non a BarDependent, poiché BarInterface non è un supertipo di FooInterface.

services:
	child:
		create: ChildClass
		autowired: FooInterface

	fooDep: FooDependent       # passa il servizio figlio al costruttore
	barDep: BarDependent       # LANCIA L'ECCEZIONE, nessun servizio passerebbe
	parentDep: ParentDependent # passa il servizio figlio al costruttore
	childDep: ChildDependent   # passa il servizio figlio al costruttore
versione: 3.x