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