DI: Passing Dependencies

Arguments, or “dependencies” in DI terminology, can be passed to classes in the following main ways:

  • passing by constructor
  • passing by method (called a setter)
  • by setting a property
  • by the inject*() method
  • by annotating inject on a property

The first three methods apply in general in all object-oriented languages, the second two are specific to Nette presenters. We will now take a closer look at each of these options and show them with specific examples.

Constructor Injection

Dependencies are passed as arguments to the constructor when the object is created:

class MyService
{
	/** @var Cache */
	private $cache;

	public function __construct(Cache $service)
	{
		$this->cache = $service;
	}
}

$service = new MyService($cache);

This form is useful for mandatory dependencies that the class absolutely needs to function, as without them the instance cannot be created.

Since PHP 8.0, we can use a shorter form of notation that is functionally equivalent:

// PHP 8.0
class MyService
{
	public function __construct(
		private Cache $service,
	) {
	}
}

As of PHP 8.1, a property can be marked with a flag readonly that declares that the contents of the property will not change:

// PHP 8.1
class MyService
{
	public function __construct(
		private readonly Cache $service,
	) {
	}
}

Setter Injection

Dependencies are passed by calling a method that stores them in a private properties. The usual naming convention for these methods is of the form set*(), which is why they are called setters.

class MyService
{
	/** @var Cache */
	private $cache;

	public function setCache(Cache $service): void
	{
		$this->cache = $service;
	}
}

$service = new MyService;
$service->setCache($cache);

This method is useful for optional dependencies that are not necessary for the class function, since it is not guaranteed that the object will actually receive them (i.e., that the user will call the method).

At the same time, this method allows the setter to be called repeatedly to change the dependency. If this is not desirable, add a check to the method, or as of PHP 8.1, mark the property $cache with the readonly flag.

class MyService
{
	/** @var Cache */
	private $cache;

	public function setCache(Cache $service): void
	{
		if ($this->cache) {
			throw new RuntimeException('The dependency has already been set');
		}
		$this->cache = $service;
	}
}

Property Injection

Dependencies are passed directly to the property:

class MyService
{
	/** @var Cache */
	public $cache;
}

$service = new MyService;
$service->cache = $cache;

This method is considered inappropriate because the property must be declared as public. Hence, we have no control over whether the passed dependency will actually be of the specified type (this was true before PHP 7.4) and we lose the ability to react to the newly assigned dependency with our own code, for example to prevent subsequent changes. At the same time, the property becomes part of the public interface of the class, which may not be desirable.

Passing by an inject*() Method

This method is specific to presenters in Nette. It is a special case of a setter, where the method starts with a prefix inject.

class MyPresenter extends Nette\Application\UI\Presenter
{
	/** @var Cache */
	private $cache;

	public function injectCache(Cache $service): void
	{
		$this->cache = $service;
	}
}

The basic difference from a setter is that Nette DI automatically calls methods named this way in presenters as soon as the instance is created, passing all required dependencies to them. Why they are used in presenters discussed below.

A presenter can contain multiple methods inject*() and each method can have any number of parameters.

Inject Annotations

Another method specific to presenters in Nette. This is a special case of passing a dependency to a public property.

In this case the property is annotated as @inject in the documentation comment. The type can also be specified in the documentation comment if you are using PHP lower than 7.4.

class MyPresenter extends Nette\Application\UI\Presenter
{
	/** @var Cache @inject */
	public $cache;
}

Since PHP 8.0, a property can be marked with an attribute Inject:

use Nette\DI\Attributes\Inject;

class MyPresenter extends Nette\Application\UI\Presenter
{
	#[Inject]
	public Cache $cache;
}

Again, Nette DI will automatically pass dependencies to properties annotated in this way in the presenter as soon as the instance is created.

This method has the same shortcomings as passing dependencies to a public property. It is used in presenter because it does not complicate the code and requires only a minimum of typing.

Which Way to Choose?

Passing dependencies through the constructor is suitable for mandatory dependencies that the class absolutely needs to function. Passing dependencies using a setter, on the other hand, is suitable for optional dependencies or dependencies that can be changed later. Passing dependencies by writing to a public property is not appropriate.

It is common to all methods that autowiring works, of course, only when objects are created via DI Container or factories. If we create an object by calling new in our own code, we must always pass the dependencies ourselves.

Which Way to Choose in Presenters?

In presenters, the preferred way to pass dependencies is through a constructor. However, if the presenter inherits from a common ancestor (e.g. BasePresenter), it is better to use the methods of inject*() in that ancestor. This is because we keep the constructor free for descendants:

abstract class BasePresenter extends Nette\Application\UI\Presenter
{
	/** @var Foo */
	private $foo;

	public function injectBase(Foo $foo): void
	{
		$this->foo = $foo;
	}
}

class MyPresenter extends BasePresenter
{
	/** @var Bar */
	private $bar;

	public function __construct(Bar $bar)
	{
		$this->bar = $bar;
	}
}

It is also possible to use annotation @inject, but it is important to keep in mind that encapsulation breaks.

Constructor injection is not recommended for common ancestors, because during inheritance it is necessary to obtain the dependencies of all parent presenters as well and forward them to parent::__construct():

abstract class BasePresenter extends Nette\Application\UI\Presenter
{
	/** @var Foo */
	private $foo;

	public function __construct(Foo $foo)
	{
		$this->foo = $foo;
	}
}

class MyPresenter extends BasePresenter
{
	/** @var Bar */
	private $bar;

	public function __construct(Foo $foo, Bar $bar)
	{
		parent::__construct($foo);
		$this->bar = $bar;
	}
}

The inject*() methods are also useful in cases where the presenter is composed of multiple traits and each of them requires its own dependency. In this case, care must be taken to ensure that the name of each inject method is unique:

class ArticlePresenter extends Nette\Application\UI\Presenter
{
	use StandardTemplateFilters;
	use RequireLoggedUser;
}

trait StandardTemplateFilters
{
	public function injectStandardTemplateFilters(TemplateBuilder $builder): void
	{
		$this->onRender[] = function () use ($builder) {
			$builder->setupTemplate($this->template);
		};
	}
}

trait RequireLoggedUser
{
	public function injectRequireLoggedUser(): void
	{
		$this->onStartup[] = function () {
			if (!$this->getUser()->isLoggedIn()) {
				$this->redirect('Sign:in', $this->storeRequest());
			}
		};
	}
}

The traits mentioned above take advantage of the fact that all inject methods are called when the presenter is created, thus performing the initialization contained in them. While trait RequireLoggedUser does not use the method to pass dependencies at all.


It is possible to pass parameters to the presenters that cannot be set by autowire by writing them into the configuration file:

services:
	- App\Presenters\ImagePresenter("%wwwDir%/media")
class ImagePresenter extends Nette\Application\UI\Presenter
{
	private $imageDir;
	private $optimizer;

	public function __construct(string $imageDir, ImageOptimizer $optimizer)
	{
		$this->imageDir = $imageDir;
		$this->optimizer = $optimizer;
	}
}

The presenter is then passed the specified string as the first parameter of the constructor, the remaining parameters are filled in using autowire.

However, when designing presenters, be careful not to include application logic that belongs to services. If you need to reconfigure the presenter, it is often a sign that you have not decomposed the problem sufficiently.


Note: by specifying a configuration directive inject: true, automatic calls of inject*() methods and dependency passing to properties marked with annotations @inject can be enabled for any service:

services:
	foo:
		factory: App\Service
		inject: true