This repository has been archived by the owner on Nov 16, 2022. It is now read-only.
forked from nette/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
di-usage.texy
445 lines (323 loc) · 13.9 KB
/
di-usage.texy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
Getting Dependencies
********************
/--div .[perex]
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":[dependency-injection], 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:
/--php
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:
/--php
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:
/--php
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.
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.
/--php
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:
/--php
class MyService
{
/** @inject @var \App\AnotherService */
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 since version 2.2.
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 [auto-wiring | configuring#auto-wiring] 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:
/--php
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:
/** @inject @var \App\Service3 */
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:
/--php
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:
/--php
class MyPresenter extends Nette\Application\UI\Presenter
{
/** @inject @var \App\Service1 */
public $service1;
/** @inject @var \App\Service2 */
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:
/--php
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.
.[caution]
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:
/--code neon
services:
service1: App\Service1
\--
All dependencies declared in the constructor of this service will be automatically passed:
/--php
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:
/--code neon
services:
service2:
class: App\Service2
setup:
- setAnotherService
\--
Class of the service:
/--php
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:
/--code neon
services:
service3:
class: App\Service3
inject: yes
\--
Dependency `Service1` will be passed by calling the `inject*` method, dependency `Service2` will be assigned to the `$service2` variable:
/--php
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:
/** @inject @var \App\Service2 */
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:
/--code neon
services:
- App\Presenters\ImagePresenter("%wwwDir%/media")
\--
/--php
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.
.[caution]
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:
/--php
namespace App\Components;
interface IUserTableFactory
{
/**
* @return UserTable
*/
public function create();
}
\--
The `create` method will instantiate an `UserTable` component with the following definition:
/--php
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:
/--code neon
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:
/--code neon
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:
/--php
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.