DI: Getting Dependencies

There are several possibilities of injecting dependencies to presenters, components and services. This article discusses:

  • dependency injection in general, not limited to the dependencies in Nette DI Container, and
  • practical examples and recommendations for presenters, components and services.

How To Get Dependencies?

Dependencies can be injected to the application classes in one of the following ways:

  • through the class constructor,
  • via setter or a member variable,
  • through the inject* method,
  • using the @inject annotation on a public member variable.

First two ways can be used in all object-oriented programming languages, last two are available in Nette Framework. Let's walk through all these ways and then show their application in practical examples.

Constructor Passing

All dependencies are passed when the object is being created. The dependency is declared as a constructor parameter and its type is given in the Type Hint:

class MyService
{
    /** @var AnotherService */
    private $anotherService;

    public function __construct(AnotherService $service)
    {
        $this->anotherService = $service;
    }
}

The MyService class declares that an instance of AnotherService class must be passed during the object creation. This declaration is suitable for all mandatory dependencies, which are required for the functioning of the class. The class could not be instantiated without these dependencies.

Passing by Setter or a Public Variable

These dependencies are passed after the object has been created. Take a look at the example of passing dependency by a setter method. The type of the dependency is given in the Type Hint:

class MyService
{
    /** @var AnotherService */
    private $anotherService;

    public function setAnotherService(AnotherService $service)
    {
        $this->anotherService = $service;
    }
}

These dependencies can be passed only after the object has been created. This method is suitable only for non-mandatory dependencies, because there is no guarantee that the dependencies will be passed to the object.

Passing by a public variable works in a very similar way:

class MyService
{
    /** @var AnotherService */
    public $anotherService;
}

However, this method is not recommended, because the variable must be declared as public and there is no way how you can ensure that the passed object will be of the given type. We also lose the ability to handle the assigned dependency in our code and we violate the principles of encapsulation.

So we will not deal further with this.

Passing by an inject* Method

This method is specific for the DI Container in Nette Framework. It is a special case of setter method which starts with an inject* prefix.

class MyService
{
    /** @var AnotherService */
    private $anotherService;

    public function injectAnotherService(AnotherService $service)
    {
        $this->anotherService = $service;
    }
}

The class can contain multiple inject* methods, each with multiple parameters and a unique name.

Nette is able to find these methods in presenters and automatically call them with proper parameters. This behaviour could be also enabled in services in configuration file. This will be explained later.

@inject Annotations

Second method specific for Nette Framework. It is a special case of passing by a public variable. The variable is marked by an @inject annotation in a docblock comment:

class MyService
{
    /** @var \App\AnotherService @inject */
    public $anotherService;
}

Nette Framework can also scan the presenter for these variables and automatically inject these dependencies. The variable type in @var annotation must be given by its fully qualified name, including namespace. Aliases defined in use directives can be used.

This approach has the same drawbacks as ordinary passing by a public variable – we cannot enforce the type of passed dependency.

However, the code is very simple and short, which can be an advantage in some cases.

Which Way Should I Choose?

Constructor passing and passing optional dependencies by setters are available for all instantiated classes. The following two techniques, passing dependencies by inject* methods and by public variables marked by the @inject annotation, are less clean techniques and are available only in presenters and services in DI container with explicit configuration. We use them only in a few specific cases.

The one thing that all these methods have in common, is that autowiring is only available for objects created by Nette DI Container or one of the factories in Nette. When we create the object by calling new, we have to pass the dependencies manually.

Lets have a look at the examples and preferred method of dependency injection.

Presenters

Presenters in Nette Framework is created by the PresenterFactory. This factory also automatically injects dependencies declared:

  1. in a constructor,
  2. using the inject* methods,
  3. by public properties with the @inject annotation.

The following presenter shows all three ways of dependency passing:

class MyPresenter extends Nette\Application\UI\Presenter
{
    // 1) Constructor passing:
    private $service1;

    public function __construct(Service1 $service)
    {
        $this->service1 = $service;
    }

    // 2) Using the inject* method:
    private $service2;

    public function injectService2(Service2 $service)
    {
        $this->service2 = $service;
    }

    // 3) Public variable with the @inject annotation:
    /** @var \App\Service3 @inject */
    public $service3;
}

The preferred way of passing dependencies for presenters is using the constructor, or in case of parent presenters using the inject* method. The usage of inject* method in parent presenters allows us to preserve encapsulation and to keep the constructor clean for child presenters.

We can also use @inject annotation for both cases, but we have to keep in mind that this breaks encapsulation.

Passing dependencies to parent presenters through the constructor is not recommended, since it complicates the constructor signature when using the presenter inheritance: you have to get and pass dependencies to all parent presenter classes.

Components

Components are usually instantiated directly in the code of presenter, or through the application-specific factories. In these cases, Nette cannot automatically inject the dependencies and you cannot use inject* methods or variables with the @inject annotations.

Lets consider we have the following component:

class MyControl extends Nette\Application\UI\Control
{
    // 1) Constructor passing:
    private $service1;

    public function __construct(Service1 $service)
    {
        parent::__construct();
        $this->service1 = $service;
    }

    // 2) Optional dependency injected by setter:
    private $service2;

    public function setService2(Service2 $service)
    {
        $this->service2 = $service;
    }
}

The component could be used in the presenter in the following way:

class MyPresenter extends Nette\Application\UI\Presenter
{
    /** @var \App\Service1 @inject */
    public $service1;

    /** @var \App\Service2 @inject */
    public $service2;

    protected function createComponentMyControl()
    {
        $control = new MyControl($this->service1);
        $control->setService2($this->service2);
        return $control;
    }
}

Because dependencies are not automatically injected by the DI container, we have to obtain all the required dependencies also in the class, that creates our component – the presenter in our example. If we create the component in another component, the component dependencies will be added to the dependencies of the parent component:

class MySecondControl extends Nette\Application\UI\Control
{
    // Dependencies for MySecondControl:
    private $service3;

    // Dependencies for the child MyControl:
    private $service1;
    private $service2;

    public function __construct(Service1 $service1, Service2 $service2, Service3 $service3)
    {
        parent::__construct();
        // assign dependencies to members $service1, $service2, $service3
    }

    protected function createComponentMyControl()
    {
        $control = new MyControl($this->service1);
        $control->setService2($this->service2);
        return $control;
    }
}

Because we usually instantiate the components by hand, the preferred way of dependency injection depends on whether the dependency is mandatory or optional. Constructor should be used for mandatory dependencies and setter for optional ones.

If we use the constructor for dependency passing, we must not forget to call the constructor from the parent class: parent::__construct()!

Services

Services are registered in the DI container and dependencies are therefore automatically passed. We can declare dependencies only in constructor, unless we provide additional configuration:

services:
    service1: App\Service1

All dependencies declared in the constructor of this service will be automatically passed:

namespace App;

class Service1
{
    private $anotherService;

    public function __construct(AnotherService $service)
    {
        $this->anotherService = $service;
    }
}

Constructor passing is the preferred way of dependency injection for services.

If we want to pass dependencies by the setter, we can add the setup section to the service definition:

services:
    service2:
        factory: App\Service2
        setup:
            - setAnotherService

Class of the service:

namespace App;

class Service2
{
    private $anotherService;

    public function setAnotherService(AnotherService $service)
    {
        $this->anotherService = $service;
    }
}

We can also add the inject: yes directive. This directive will enable automatic call of inject* methods and passing dependencies to public variables with @inject annotations:

services:
    service3:
        factory: App\Service3
        inject: yes

Dependency Service1 will be passed by calling the inject* method, dependency Service2 will be assigned to the $service2 variable:

namespace App;

class Service3
{
    // 1) inject* method:
    private $service1;

    public function injectService1(Service1 $service)
    {
        $this->service1 = $service1;
    }

    // 2) Assign to the variable with the @inject annotation:
    /** @var \App\Service2 @inject */
    public $service2;
}

Other Possibilities

There are also a few other possibilities, how we can change the configuration and dependency injection for presenters and components.

Presenter as a Service

Beginning with the Nette 2.1, you can register the presenter as a service to the configuration file. It will be created as any other service in the DI container. You can pass any parameters that could not be auto-wired (strings, numbers, etc.), and add setter calls.

All inject* methods will be called automatically and all dependencies will be automatically assigned to the public variables with the @inject annotation. You do not have to add the inject: yes directive.

The presenter definition in the configuration file could look as following:

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

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

The string from the configuration will be passed as the first parameter of the constructor, the remaining parameters will be auto-wired.

However, you have to be careful when designing presenters. All application logic should be in services, not in presenters. The need of additional presenter configuration is often a sign of a bad decomposition of the challenge.

Component Factory

Like presenters, components can be also registered in the configuration file. However, the presenter is usually created only once during the handling of a single request, while components can be instantiated at multiple locations. Therefore we have to register a component factory instead of a service.

Beginning with Nette 2.1, we can use factories generated from an interface. The interface must declare the returning type in the @return annotation of the method. Nette will generate a proper implementation of the interface.

The interface must have exactly one method named create. Our component factory interface could be declared in the following way:

namespace App\Components;

interface IUserTableFactory
{
    /**
     * @return UserTable
     */
    public function create();
}

The create method will instantiate an UserTable component with the following definition:

namespace App\Components;

class UserTable extends Control
{
    private $userManager;

    public function __construct(UserManager $userManager)
    {
        $this->userManager = $userManager;
    }
}

The factory will be registered in the config.neon file:

services:
    - App\Components\IUserTableFactory

Nette will check if the declared service is an interface. If yes, it will also generate the corresponding implementation of the factory. The definition can be also written in a more verbose form:

services:
    userTableFactory:
        implement: App\Components\IUserTableFactory

This full definition allows us to declare additional configuration of the component using the arguments and setup sections, similarly as for all other services.

In the presenter, we only have to obtain the factory instance and call the create method:

class UserPresenter extends Nette\Application\UI\Presenter
{
    /** @var \App\Components\IUserTableFactory @inject */
    public $userTableFactory;

    protected function createComponentUserTable()
    {
        return $this->userTableFactory->create();
    }
}

Constructor dependencies will be automatically passed to the created control.