Autowiring
L'Autowiring è una funzionalità fantastica che può passare automaticamente i servizi richiesti al costruttore e ad altri metodi, quindi non dobbiamo scriverli affatto. Ti fa risparmiare un sacco di tempo.
Grazie a questo, possiamo omettere la stragrande maggioranza degli argomenti quando scriviamo le definizioni dei servizi. Invece di:
services:
articles: Model\ArticleRepository(@database, @cache.storage)
Basta scrivere:
services:
articles: Model\ArticleRepository
L'Autowiring si basa sui tipi, quindi affinché funzioni, la classe ArticleRepository
deve essere definita più
o meno così:
namespace Model;
class ArticleRepository
{
public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
{}
}
Per poter utilizzare l'autowiring, deve esserci esattamente un servizio per ogni tipo nel container. 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 ECCEZIONE, soddisfano sia mainDb che tempDb
La soluzione sarebbe bypassare l'autowiring e specificare esplicitamente il nome del servizio (cioè
articles: Model\ArticleRepository(@mainDb)
). Ma è più intelligente disattivare l'autowiring per uno dei servizi, o dare la preferenza al primo servizio.
Disattivazione dell'autowiring
Possiamo disattivare l'autowiring di un servizio usando l'opzione autowired: no
:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
create: PDO('sqlite::memory:')
autowired: false # il servizio tempDb è escluso dall'autowiring
articles: Model\ArticleRepository # quindi passa mainDb al costruttore
Il servizio articles
non lancerà un'eccezione perché esistono due servizi compatibili 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 diversamente rispetto a Symfony, dove l'opzione
autowire: false
indica che l'autowiring non deve essere utilizzato per gli argomenti del costruttore del servizio
specificato. In Nette, l'autowiring viene sempre utilizzato, sia per gli argomenti del costruttore che per qualsiasi altro metodo.
L'opzione autowired: false
indica che l'istanza del servizio specificato non deve essere passata da nessuna parte
tramite autowiring.
Preferenza dell'autowiring
Se abbiamo più servizi dello stesso tipo e per uno di essi specifichiamo l'opzione autowired
, questo servizio
diventa preferito:
services:
mainDb:
create: PDO(%dsn%, %user%, %password%)
autowired: PDO # diventa preferito
tempDb:
create: PDO('sqlite::memory:')
articles: Model\ArticleRepository
Il servizio articles
non lancerà un'eccezione perché esistono due servizi compatibili di tipo PDO
(cioè mainDb
e tempDb
), ma utilizzerà il servizio preferito, ovvero mainDb
.
Array di servizi
L'Autowiring può anche passare array di servizi di un certo tipo. Poiché in PHP non è possibile scrivere nativamente il tipo
degli elementi dell'array, è necessario aggiungere, oltre al tipo array
, anche un commento phpDoc con il tipo
dell'elemento nella forma ClassName[]
:
namespace Model;
class ShipManager
{
/**
* @param Shipper[] $shippers
*/
public function __construct(array $shippers)
{}
}
Il container DI passerà quindi automaticamente un array di servizi corrispondenti al tipo specificato. Ometterà i servizi che hanno l'autowiring disattivato.
Il tipo nel commento può anche essere nella forma array<int, Class>
o list<Class>
. Se
non puoi influenzare la forma del commento phpDoc, puoi passare l'array di servizi direttamente nella configurazione usando typed()
.
Argomenti scalari
L'Autowiring può fornire solo oggetti e array di oggetti. Gli argomenti scalari (ad es. stringhe, numeri, booleani) li scriviamo nella configurazione. Un'alternativa è creare un oggetto-impostazioni, che incapsula il valore scalare (o più valori) in un oggetto, che può poi essere nuovamente passato tramite autowiring.
class MySettings
{
public function __construct(
// readonly può essere usato da PHP 8.1
public readonly bool $value,
)
{}
}
Lo trasformi in un servizio aggiungendolo alla configurazione:
services:
- MySettings('any value')
Tutte le classi lo richiederanno quindi tramite autowiring.
Restrizione dell'autowiring
Per singoli servizi, l'autowiring può essere ristretto a determinate classi o interfacce.
Normalmente, l'autowiring passa un servizio a ogni parametro di metodo il cui tipo corrisponde al servizio. La restrizione significa che stabiliamo condizioni che i tipi specificati nei parametri dei metodi devono soddisfare affinché il servizio venga loro passato.
Lo mostreremo con 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, l'autowiring fallirebbe:
services:
parent: ParentClass
child: ChildClass
parentDep: ParentDependent # LANCIA ECCEZIONE, soddisfano i servizi parent e child
childDep: ChildDependent # l'autowiring passa il servizio child al costruttore
Il servizio parentDep
lancerà l'eccezione
Multiple services of type ParentClass found: parent, child
, perché entrambi i servizi parent
e
child
corrispondono al suo costruttore, e l'autowiring non può decidere quale scegliere.
Per il servizio child
, possiamo quindi restringere il suo autowiring al tipo ChildClass
:
services:
parent: ParentClass
child:
create: ChildClass
autowired: ChildClass # si può anche scrivere 'autowired: self'
parentDep: ParentDependent # l'autowiring passa il servizio parent al costruttore
childDep: ChildDependent # l'autowiring passa il servizio child al costruttore
Ora, al costruttore del servizio parentDep
viene passato il servizio parent
, perché ora è l'unico
oggetto compatibile. L'autowiring non passerà più il servizio child
lì. Sì, il servizio child
è
ancora di tipo ParentClass
, ma la condizione restrittiva data per il tipo del parametro non è più valida, cioè non
è vero che ParentClass
è un supertipo di ChildClass
.
Per il servizio child
, autowired: ChildClass
potrebbe anche essere scritto come
autowired: self
, poiché self
è un segnaposto per la classe del servizio corrente.
Nella chiave autowired
è possibile specificare anche più classi o interfacce come array:
autowired: [BarClass, FooInterface]
Proviamo a completare l'esempio con un'interfaccia:
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 in alcun modo il servizio child
, corrisponderà ai costruttori di tutte le classi
FooDependent
, BarDependent
, ParentDependent
e ChildDependent
e l'autowiring lo
passerà lì.
Ma se restringiamo il suo autowiring a ChildClass
usando autowired: ChildClass
(o self
),
l'autowiring lo passerà solo al costruttore di ChildDependent
, perché richiede un argomento di tipo
ChildClass
ed è vero che ChildClass
è di tipo ChildClass
. Nessun altro tipo
specificato negli altri parametri è un supertipo di ChildClass
, quindi il servizio non viene passato.
Se lo limitiamo a ParentClass
usando autowired: ParentClass
, l'autowiring lo passerà di nuovo al
costruttore di ChildDependent
(perché il ChildClass
richiesto è un supertipo di
ParentClass
) e ora anche al costruttore di ParentDependent
, perché anche il tipo
ParentClass
richiesto è compatibile.
Se lo limitiamo a FooInterface
, sarà ancora autowired in ParentDependent
(il
ParentClass
richiesto è un supertipo di FooInterface
) e ChildDependent
, ma inoltre anche
nel costruttore di FooDependent
, ma non in BarDependent
, perché BarInterface
non è un
supertipo di FooInterface
.
services:
child:
create: ChildClass
autowired: FooInterface
fooDep: FooDependent # l'autowiring passa child al costruttore
barDep: BarDependent # LANCIA ECCEZIONE, nessun servizio corrisponde
parentDep: ParentDependent # l'autowiring passa child al costruttore
childDep: ChildDependent # l'autowiring passa child al costruttore