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:
		create: 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.

Configuring autowiring in Nette works differently than in Symfony, where the autowire: false option says that autowiring should not be used for service constructor arguments. In Nette, autowiring is always used, whether for arguments of the constructor or any other method. The autowired: false option says that the service instance should not be passed anywhere using autowiring.

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:
		create: PDO(%dsn%, %user%, %password%)
		autowired: PDO    # makes it preferred

	tempDb:
		create: 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.

Collection of Services

Autowiring can also pass an array of services of a particular type. Since PHP cannot natively notate the type of array items, in addition to the array type, a phpDoc comment with the item type like ClassName[] must be added:

namespace Model;

class ShipManager
{
	/**
	 * @param Shipper[] $shippers
	 */
	public function __construct(array $shippers)
	{}
}

The DI container then automatically passes an array of services matching the given type. It will omit services that have autowiring turned off.

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 can only pass objects and arrays of objects. Scalar arguments (e.g. strings, numbers, booleans) write in configuration. An alternative is to create a settings-object that encapsulates a scalar value (or multiple values) as an object, which can then be passed again using autowiring.

class MySettings
{
	public function __construct(
		// readonly can be used since PHP 8.1
		public readonly bool $value,
	)
	{}
}

You create a service by adding it to the configuration:

services:
	- MySettings('any value')

All classes will then request it via autowiring.

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:
		create: 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:
		create: 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