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 into 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 apublic
member variable.
The first two ways can be used in all object-oriented programming languages, the 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 dependency is given in the Type Hint:
class MyService
{
/** @var AnotherService */
private $anotherService;
public function setAnotherService(AnotherService $service): void
{
$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 the setter method which starts with an
inject*
prefix.
class MyService
{
/** @var AnotherService */
private $anotherService;
public function injectAnotherService(AnotherService $service): void
{
$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 behavior could be also enabled in services in the 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 AnotherService @inject */
public $anotherService;
}
Or in PHP 8 using the attribute:
use Nette\DI\Attributes\Inject;
class MyService
{
#[Inject]
public AnotherService $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.
Let's have a look at the examples and preferred method of dependency injection.
Presenters
Presenters in Nette Framework are created by the PresenterFactory
. This factory also automatically injects
dependencies declared:
- in a constructor,
- using the
inject*
methods, - 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): void
{
$this->service2 = $service;
}
// 3) Public variable with the @inject annotation:
/** @var 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.
Let's consider we have the following component:
class MyControl extends Nette\Application\UI\Control
{
// 1) Constructor passing:
private $service1;
public function __construct(Service1 $service)
{
$this->service1 = $service;
}
// 2) Optional dependency injected by setter:
private $service2;
public function setService2(Service2 $service): void
{
$this->service2 = $service;
}
}
The component could be used in the presenter in the following way:
class MyPresenter extends Nette\Application\UI\Presenter
{
/** @var Service1 */
private $service1;
/** @var Service2 */
private $service2;
public function __construct(Service1 $service1, Service2 $service2)
{
$this->service1 = $service1;
$this->service2 = $service2;
}
protected function createComponentMyControl(): MyControl
{
$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)
{
// assign dependencies to members $service1, $service2, $service3
}
protected function createComponentMyControl(): MyControl
{
$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.
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): void
{
$this->anotherService = $service;
}
}
We can also add the inject: true
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: true
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): void
{
$this->service1 = $service1;
}
// 2) Assign to the variable with the @inject annotation:
/** @var 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: true
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(string $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 UserTableFactory
{
public function create(): UserTable;
}
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 configuration file:
services:
- App\Components\UserTableFactory
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\UserTableFactory
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\UserTableFactory @inject */
public $userTableFactory;
protected function createComponentUserTable(): UserTable
{
return $this->userTableFactory->create();
}
}
Constructor dependencies will be automatically passed to the created control.
Related blog posts
Dependency Injection: intro (1/6)
Do you remember your first program? We don't know what language it was written in, but if it were PHP 7, it would probably look something like this:…
How to automatically register classes into DIC
Many of you didn't know the fact that Nette 3 has a built-in extension for automatic registration of your classes to dependency injection container.…
Powerful Dependency Injection Container
Nette DI (Dependency Injection Container) is one of the most interesting parts of the Nette Framework. It is compiled, extremely fast and easy to…