Autowiring
Autowiring es una gran característica que puede pasar automáticamente los servicios requeridos al constructor y otros métodos, por lo que no tenemos que escribirlos en absoluto. Le ahorrará mucho tiempo.
Gracias a esto, podemos omitir la gran mayoría de los argumentos al escribir definiciones de servicios. En lugar de:
services:
articles: Model\ArticleRepository(@database, @cache.storage)
Simplemente escriba:
services:
articles: Model\ArticleRepository
Autowiring se guía por tipos, por lo que para que funcione, la clase ArticleRepository
debe definirse de la
siguiente manera:
namespace Model;
class ArticleRepository
{
public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
{}
}
Para poder usar autowiring, debe haber exactamente un servicio para cada tipo en el contenedor. Si hubiera más, autowiring no sabría cuál pasar y lanzaría una excepción:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb: PDO('sqlite::memory:')
articles: Model\ArticleRepository # LANZARÁ EXCEPCIÓN, coinciden mainDb y tempDb
La solución sería omitir autowiring y especificar explícitamente el nombre del servicio (es decir,
articles: Model\ArticleRepository(@mainDb)
). Pero es más inteligente desactivar el autowiring de uno de los servicios, o dar
preferencia al primer servicio.
Desactivación de autowiring
Podemos desactivar el autowiring de un servicio usando la opción autowired: no
:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
create: PDO('sqlite::memory:')
autowired: false # el servicio tempDb está excluido de autowiring
articles: Model\ArticleRepository # por lo tanto, pasa mainDb al constructor
El servicio articles
no lanzará una excepción porque existen dos servicios compatibles de tipo PDO
(es decir, mainDb
y tempDb
) que se pueden pasar al constructor, ya que solo ve el servicio
mainDb
.
La configuración de autowiring en Nette funciona de manera diferente que en Symfony, donde la opción
autowire: false
indica que no se debe usar autowiring para los argumentos del constructor del servicio dado. En
Nette, autowiring siempre se usa, ya sea para los argumentos del constructor o cualquier otro método. La opción
autowired: false
indica que la instancia del servicio dado no debe pasarse a ningún lugar mediante autowiring.
Preferencia de autowiring
Si tenemos varios servicios del mismo tipo y especificamos la opción autowired
para uno de ellos, este servicio
se convierte en el preferido:
services:
mainDb:
create: PDO(%dsn%, %user%, %password%)
autowired: PDO # se convierte en el preferido
tempDb:
create: PDO('sqlite::memory:')
articles: Model\ArticleRepository
El servicio articles
no lanzará una excepción porque existen dos servicios compatibles de tipo PDO
(es decir, mainDb
y tempDb
), sino que utilizará el servicio preferido, es decir,
mainDb
.
Array de servicios
Autowiring también puede pasar arrays de servicios de un tipo específico. Dado que en PHP no se puede escribir nativamente el
tipo de los elementos del array, es necesario, además del tipo array
, agregar un comentario phpDoc con el tipo del
elemento en el formato ClassName[]
:
namespace Model;
class ShipManager
{
/**
* @param Shipper[] $shippers
*/
public function __construct(array $shippers)
{}
}
El contenedor DI luego pasa automáticamente un array de servicios que coinciden con el tipo dado. Omite los servicios que tienen autowiring desactivado.
El tipo en el comentario también puede tener el formato array<int, Class>
o list<Class>
.
Si no puede influir en la forma del comentario phpDoc, puede pasar el array de servicios directamente en la configuración usando
typed()
.
Argumentos escalares
Autowiring solo puede inyectar objetos y arrays de objetos. Los argumentos escalares (por ejemplo, cadenas, números, booleanos) los escribimos en la configuración. Una alternativa es crear un objeto de configuración, que encapsula el valor escalar (o múltiples valores) en forma de objeto, y este luego se puede pasar nuevamente mediante autowiring.
class MySettings
{
public function __construct(
// readonly se puede usar desde PHP 8.1
public readonly bool $value,
)
{}
}
Lo convierte en un servicio agregándolo a la configuración:
services:
- MySettings('any value')
Todas las clases lo solicitarán luego mediante autowiring.
Restricción de autowiring
Para servicios individuales, autowiring se puede restringir solo a ciertas clases o interfaces.
Normalmente, autowiring pasa el servicio a cada parámetro del método cuyo tipo coincide con el servicio. La restricción significa que establecemos condiciones que deben cumplir los tipos especificados en los parámetros del método para que se les pase el servicio.
Lo mostraremos con un ejemplo:
class ParentClass
{}
class ChildClass extends ParentClass
{}
class ParentDependent
{
function __construct(ParentClass $obj)
{}
}
class ChildDependent
{
function __construct(ChildClass $obj)
{}
}
Si los registráramos todos como servicios, autowiring fallaría:
services:
parent: ParentClass
child: ChildClass
parentDep: ParentDependent # LANZARÁ EXCEPCIÓN, coinciden los servicios parent y child
childDep: ChildDependent # autowiring pasa el servicio child al constructor
El servicio parentDep
lanzará una excepción
Multiple services of type ParentClass found: parent, child
, porque ambos servicios parent
y
child
encajan en su constructor, y autowiring no puede decidir cuál elegir.
Por lo tanto, para el servicio child
, podemos restringir su autowiring al tipo ChildClass
:
services:
parent: ParentClass
child:
create: ChildClass
autowired: ChildClass # también se puede escribir 'autowired: self'
parentDep: ParentDependent # autowiring pasa el servicio parent al constructor
childDep: ChildDependent # autowiring pasa el servicio child al constructor
Ahora, el servicio parent
se pasa al constructor del servicio parentDep
, porque ahora es el único
objeto compatible. Autowiring ya no pasa el servicio child
allí. Sí, el servicio child
sigue siendo de
tipo ParentClass
, pero la condición de restricción dada para el tipo de parámetro ya no se cumple, es decir, no es
cierto que ParentClass
es un supertipo de ChildClass
.
Para el servicio child
, autowired: ChildClass
también podría escribirse como
autowired: self
, ya que self
es un marcador de posición para la clase del servicio actual.
En la clave autowired
, también es posible especificar varias clases o interfaces como un array:
autowired: [BarClass, FooInterface]
Intentemos complementar el ejemplo con interfaces:
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)
{}
}
Si no restringimos el servicio child
de ninguna manera, encajará en los constructores de todas las clases
FooDependent
, BarDependent
, ParentDependent
y ChildDependent
, y autowiring lo
pasará allí.
Pero si restringimos su autowiring a ChildClass
usando autowired: ChildClass
(o self
),
autowiring solo lo pasará al constructor de ChildDependent
, porque requiere un argumento de tipo
ChildClass
y es cierto que ChildClass
es de tipo ChildClass
. Ningún otro tipo
especificado en los otros parámetros es un supertipo de ChildClass
, por lo que el servicio no se pasa.
Si lo restringimos a ParentClass
usando autowired: ParentClass
, autowiring lo pasará nuevamente al
constructor de ChildDependent
(porque el ChildClass
requerido es un supertipo de
ParentClass
) y ahora también al constructor de ParentDependent
, porque el tipo requerido
ParentClass
también es compatible.
Si lo restringimos a FooInterface
, seguirá siendo autowired en ParentDependent
(el
ParentClass
requerido es un supertipo de FooInterface
) y ChildDependent
, pero además
también en el constructor de FooDependent
, pero no en BarDependent
, porque BarInterface
no
es un supertipo de FooInterface
.
services:
child:
create: ChildClass
autowired: FooInterface
fooDep: FooDependent # autowiring pasa child al constructor
barDep: BarDependent # LANZARÁ EXCEPCIÓN, ningún servicio coincide
parentDep: ParentDependent # autowiring pasa child al constructor
childDep: ChildDependent # autowiring pasa child al constructor