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.
Se non si può controllare la forma del commento di phpDoc, si può passare un array di servizi direttamente nella
configurazione, usando il metodo 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