Autowiring
Autowiring is a great feature that automatically passes required services to the constructor and other methods, so we don't have to specify them explicitly. It saves you a lot of time.
Thanks to this, we can omit the vast majority of arguments when writing service definitions. Instead of:
services:
articles: Model\ArticleRepository(@database, @cache.storage)
Just write:
services:
articles: Model\ArticleRepository
Autowiring is guided by types, so for it to work, the ArticleRepository
class must be defined roughly as
follows:
namespace Model;
class ArticleRepository
{
public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
{}
}
To be able to use autowiring, there must be exactly one service of each type in the container. If there were more, autowiring wouldn't know which one to pass and would throw an exception:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb: PDO('sqlite::memory:')
articles: Model\ArticleRepository # THROWS EXCEPTION, both mainDb and tempDb match
The solution is to either bypass autowiring and explicitly specify the service name (e.g.,
articles: Model\ArticleRepository(@mainDb)
). However, a more convenient approach is to either disable autowiring for one of the services, or to prefer one service over the others.
Disabling Autowiring
We can disable autowiring for a service using the autowired: false
option:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
create: PDO('sqlite::memory:')
autowired: false # service tempDb is excluded from autowiring
articles: Model\ArticleRepository # therefore passes mainDb to the constructor
The articles
service will not throw an exception about two matching PDO
services (mainDb
and tempDb
) being available for the constructor, because it only considers the mainDb
service.
Autowiring configuration in Nette differs from Symfony. In Symfony, autowire: false
means autowiring
shouldn't be used for the service's constructor arguments. In Nette, autowiring applies to constructor arguments and any other
methods invoked via the container (like setter injection). The autowired: false
option prevents the container from
automatically passing this service instance as a dependency to other services.
Autowiring Preference
If we have multiple services of the same type and specify the autowired
option for one of them, this service
becomes the preferred one:
services:
mainDb:
create: PDO(%dsn%, %user%, %password%)
autowired: PDO # becomes preferred
tempDb:
create: PDO('sqlite::memory:')
articles: Model\ArticleRepository
The articles
service will not throw an exception about multiple matching PDO
services
(mainDb
and tempDb
), but will use the preferred one, which is mainDb
.
Collection of Services
Autowiring can also pass arrays of services of a specific type. Since PHP doesn't natively support specifying array item types
in type hints, you must supplement the array
type hint with a phpDoc comment specifying the item type, like
ClassName[]
:
namespace Model;
class ShipManager
{
/**
* @param Shipper[] $shippers
*/
public function __construct(array $shippers)
{}
}
The DI container then automatically passes an array of services corresponding to the given type. It omits services that have autowiring disabled.
The type in the comment can also be of the form array<int, Class>
or list<Class>
. If you
can't control the form of the phpDoc comment, you can pass an array of services directly in the configuration using typed()
.
Scalar Arguments
Autowiring only works for objects and arrays of objects. Scalar arguments (e.g., strings, numbers, booleans) must be specified in the configuration. An alternative is to create a settings object that encapsulates the scalar value (or multiple values). This object can then be passed using autowiring.
class MySettings
{
public function __construct(
// readonly can be used since PHP 8.1
public readonly bool $value,
)
{}
}
You register it as a service by adding it to the configuration:
services:
- MySettings('any value')
Other classes can then request it via autowiring.
Narrowing Autowiring
For individual services, autowiring can be narrowed down to specific classes or interfaces.
Normally, autowiring passes a service to every method parameter whose type the service matches. Narrowing means we establish conditions that the types specified for the method parameters must meet for the service to be passed to them.
Let's take an example:
class ParentClass
{}
class ChildClass extends ParentClass
{}
class ParentDependent
{
function __construct(ParentClass $obj)
{}
}
class ChildDependent
{
function __construct(ChildClass $obj)
{}
}
If we registered them all as services, autowiring would fail:
services:
parent: ParentClass
child: ChildClass
parentDep: ParentDependent # THROWS EXCEPTION, both parent and child services match
childDep: ChildDependent # autowiring passes the child service to the constructor
The parentDep
service throws the exception
Multiple services of type ParentClass found: parent, child
, because both the parent
and
child
services fit into its constructor, and autowiring cannot decide which one to choose.
For the child
service, we can therefore narrow its autowiring to the type ChildClass
:
services:
parent: ParentClass
child:
create: ChildClass
autowired: ChildClass # can also be written as 'autowired: self'
parentDep: ParentDependent # autowiring passes the parent service to the constructor
childDep: ChildDependent # autowiring passes the child service to the constructor
Now, the parent
service is passed to the parentDep
service's constructor, as it is now the only
matching object. The child
service is no longer passed there by autowiring. Yes, the child
service is
still of type ParentClass
, but the narrowing condition autowired: ChildClass
means it will only be
passed to parameters explicitly typed as ChildClass
(or its subtypes). Since ParentDependent
requires
ParentClass
, the child
service is no longer considered a candidate for autowiring there.
For the child
service, autowired: ChildClass
could also be written as autowired: self
,
since self
is a placeholder for the current service's class.
In the autowired
key, it is also possible to specify multiple classes or interfaces as an array:
autowired: [BarClass, FooInterface]
Let's try to add interfaces to the example:
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)
{}
}
If we don't restrict the child
service in any way, it will fit into the constructors of all
FooDependent
, BarDependent
, ParentDependent
, and ChildDependent
classes, and
autowiring will pass it there.
However, if we narrow its autowiring to ChildClass
using autowired: ChildClass
(or
self
), autowiring will only pass it to the ChildDependent
constructor, because it requires an argument
of type ChildClass
and it holds that ChildClass
is of type ChildClass
. No other
type specified for the other parameters is a supertype of ChildClass
, so the service is not passed.
If we restrict it to ParentClass
using autowired: ParentClass
, autowiring will again pass it to the
ChildDependent
constructor (because the required ChildClass
is a supertype of ParentClass
)
and now also to the ParentDependent
constructor, because the required type ParentClass
is also
suitable.
If we restrict it to FooInterface
, it will still be autowired into ParentDependent
(the required
ParentClass
is a supertype of FooInterface
) and ChildDependent
, but additionally also into
the FooDependent
constructor, but not into BarDependent
, because BarInterface
is not a
supertype of FooInterface
.
services:
child:
create: ChildClass
autowired: FooInterface
fooDep: FooDependent # autowiring passes the child service to the constructor
barDep: BarDependent # THROWS EXCEPTION, no service matches
parentDep: ParentDependent # autowiring passes the child service to the constructor
childDep: ChildDependent # autowiring passes the child service to the constructor