Autowiring
Autowiring is a great feature that can automatically pass services to the constructor and other methods, so we do not need to write them at all. It saves you a lot of time.
This allows us to skip 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 driven by types, so ArticleRepository
class must be defined as follows:
namespace Model;
class ArticleRepository
{
public function __construct(\PDO $db, \Nette\Caching\Storage $storage)
{}
}
To use autowiring, there must be just one service for each type in the container. If there were more, autowiring would not know which one to pass and throw away an exception:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb: PDO('sqlite::memory:')
articles: Model\ArticleRepository # THROWS EXCEPTION, both mainDb and tempDb matches
The solution would be to either bypass autowiring and explicitly state the service name (i.e.
articles: Model\ArticleRepository(@mainDb)
). However, it is more convenient to disable autowiring of one services, or the first service prefer.
Disabled Autowiring
You can disable service autowiring by using the autowired: no
option:
services:
mainDb: PDO(%dsn%, %user%, %password%)
tempDb:
factory: PDO('sqlite::memory:')
autowired: false # removes tempDb from autowiring
articles: Model\ArticleRepository # therefore passes mainDb to constructor
The articles
service does not throw the exception that there are two matching services of type PDO
(i.e. mainDb
and tempDb
) that can be passed to the constructor, because it only sees the
mainDb
service.
Preferred Autowiring
If we have more services of the same type and one of them has the autowired
option, this service becomes the
preferred one:
services:
mainDb:
factory: PDO(%dsn%, %user%, %password%)
autowired: PDO # makes it preferred
tempDb:
factory: PDO('sqlite::memory:')
articles: Model\ArticleRepository
The articles
service does not throw the exception that there are two matching PDO
services (i.e.
mainDb
and tempDb
), but uses the preferred service, i.e. mainDb
.
Narrowing of Autowiring
For individual services, autowiring can be narrowed to specific classes or interfaces.
Normally, autowiring passes the service to each method parameter whose type the service corresponds to. Narrowing means that we specify conditions that the types specified for the method parameters must satisfy 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 matches
childDep: ChildDependent # passes the service 'child' to the constructor
The parentDep
service throws the exception Multiple services of type ParentClass found: parent, child
because both parent
and child
fit into its constructor and autowiring can not make a decision on which
one to choose.
For service child
, we can therefore narrow down its autowiring to ChildClass
:
services:
parent: ParentClass
child:
factory: ChildClass
autowired: ChildClass # alternative: 'autowired: self'
parentDep: ParentDependent # THROWS EXCEPTION, the 'child' can not be autowired
childDep: ChildDependent # passes the service 'child' to the constructor
The parentDep
service is now passed to the parentDep
service constructor, since it is now the only
matching object. The child
service is no longer passed in by autowiring. Yes, the child
service is still
of type ParentClass
, but the narrowing condition given for the parameter type no longer applies, i.e. it is no longer
true that ParentClass
is a supertype of ChildClass
.
In the case of child
, autowired: ChildClass
could be written as autowired: self
as the
self
means current service type.
The autowired
key can include several classes and interfaces as 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)
{}
}
When we do not limit the child
service, 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 it only passes it to the ChildDependent
constructor, because it requires an argument
of type ChildClass
and ChildClass
is of type ChildClass
. No other type specified
for the other parameters is a superset of ChildClass
, so the service is not passed.
If we restrict it to ParentClass
using autowired: ParentClass
, autowiring will pass it again to the
ChildDependent
constructor (since the required type ChildClass
is a superset of
ParentClass
) and to the ParentDependent
constructor too, since the required type of
ParentClass
is also matching.
If we restrict it to FooInterface
, it will still autowire to ParentDependent
(the required type
ParentClass
is a supertype of FooInterface
) and ChildDependent
, but additionally to the
FooDependent
constructor, but not to BarDependent
, since BarInterface
is not a supertype of
FooInterface
.
services:
child:
factory: ChildClass
autowired: FooInterface
fooDep: FooDependent # passes the service child to the constructor
barDep: BarDependent # THROWS EXCEPTION, no service would pass
parentDep: ParentDependent # passes the service child to the constructor
childDep: ChildDependent # passes the service child to the constructor