Skip to content

Commit 912f91c

Browse files
committed
readme.md: completely rewritten
1 parent 72bc3f0 commit 912f91c

File tree

1 file changed

+301
-34
lines changed

1 file changed

+301
-34
lines changed

readme.md

Lines changed: 301 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,344 @@
1-
Nette Dependency Injection
2-
==========================
1+
Nette Dependency Injection (DI)
2+
===============================
33

44
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/di.svg)](https://packagist.org/packages/nette/di)
55
[![Build Status](https://travis-ci.org/nette/di.svg?branch=master)](https://travis-ci.org/nette/di)
66

77
Purpose of the Dependecy Injection (DI) is to free classes from the responsibility for obtaining objects that they need for its operation (these objects are called **services**). To pass them these services on their instantiation instead.
88

9-
Class `Nette\DI\Container` is a flexible implementation of the universal DI container. It ensures automatically, that instance of services are created only once.
9+
Nette DI is one of the most interesting part of framework. It is compiled DI container, extremely fast and easy to configure.
1010

11-
Names of factory methods follow an uniform convention, they consist of the prefix `createService` + name of the service starting with first letter upper-cased. If they are not supposed to be accesible from outside, it is possible to lower their visibility to `protected`. Note that the container has already defined the field `$parameters` for user parameters.
11+
Let's have an application for sending newsletters. The code is maximally simplified and is available on the [GitHub](https://github.com/dg/di-example).
12+
13+
We have the object representing email:
14+
15+
```php
16+
class Mail
17+
{
18+
public $subject;
19+
public $message;
20+
}
21+
```
22+
23+
An object which can send emails:
24+
25+
```php
26+
interface Mailer
27+
{
28+
function send(Mail $mail, $to);
29+
}
30+
```
31+
32+
A support for logging:
33+
34+
```php
35+
interface Logger
36+
{
37+
function log($message);
38+
}
39+
```
40+
41+
And finally, a class that provides sending newsletters:
42+
43+
```php
44+
class NewsletterManager
45+
{
46+
private $mailer;
47+
private $logger;
48+
49+
function __construct(Mailer $mailer, Logger $logger)
50+
{
51+
$this->mailer = $mailer;
52+
$this->logger = $logger;
53+
}
54+
55+
function distribute(array $recipients)
56+
{
57+
$mail = new Mail;
58+
...
59+
foreach ($recipients as $recipient) {
60+
$this->mailer->send($mail, $recipient);
61+
}
62+
$this->logger->log(...);
63+
}
64+
}
65+
```
66+
67+
The code respects Dependency Injection, ie. **each object uses only variables which we had passed into it.**
68+
69+
Also, we have a ability to implement own `Logger` or `Mailer`, like this:
70+
71+
```php
72+
class SendMailMailer implements Mailer
73+
{
74+
function send(Mail $mail, $to)
75+
{
76+
mail($to, $mail->subject, $mail->message);
77+
}
78+
}
79+
80+
class FileLogger implements Logger
81+
{
82+
private $file;
83+
84+
function __construct($file)
85+
{
86+
$this->file = $file;
87+
}
88+
89+
function log($message)
90+
{
91+
file_put_contents($this->file, $message . "\n", FILE_APPEND);
92+
}
93+
}
94+
```
95+
96+
**DI container is the supreme architect** which can create individual objects (in the terminology DI called services) and assemble and configure them exactly according to our needs.
97+
98+
Container for our application might look like this:
1299

13100
```php
14-
class MyContainer extends Nette\DI\Container
101+
class Container
15102
{
103+
private $logger;
104+
private $mailer;
16105

17-
protected function createServiceConnection()
106+
function getLogger()
18107
{
19-
return new Nette\Database\Connection(
20-
$this->parameters['dsn'],
21-
$this->parameters['user'],
22-
$this->parameters['password']
23-
);
108+
if (!$this->logger) {
109+
$this->logger = new FileLogger('log.txt');
110+
}
111+
return $this->logger;
24112
}
25113

26-
protected function createServiceArticle()
114+
function getMailer()
27115
{
28-
return new Article($this->connection);
116+
if (!$this->mailer) {
117+
$this->mailer = new SendMailMailer;
118+
}
119+
return $this->mailer;
29120
}
30121

122+
function createNewsletterManager()
123+
{
124+
return new NewsletterManager($this->getMailer(), $this->getLogger());
125+
}
31126
}
32127
```
33128

34-
Now we create an instance of the container and pass parameters:
129+
The implementation looks like this because:
130+
- the individual services are created only on demand (lazy loading)
131+
- doubly called `createNewsletterManager` will use the same logger and mailer instances
132+
133+
Let's instantiate `Container`, let it create manager and we can start spamming users with newsletters :-)
35134

36135
```php
37-
$container = new MyContainer(array(
38-
'dsn' => 'mysql:',
39-
'user' => 'root',
40-
'password' => '***',
41-
));
136+
$container = new Container;
137+
$manager = $container->createNewsletterManager();
138+
$manager->distribute(...);
42139
```
43140

44-
We get the service by calling the `getService` method or by a shortcut:
141+
Significant to Dependency Injection is that no class depends on the container. Thus it can be easily replaced with another one. For example with the container generated by Nette DI.
142+
143+
Nette DI
144+
----------
145+
146+
Nette DI is the generator of containers. We instruct it (usually) with configuration files. This is configuration that leads to generate nearly the same class as the class `Container` above:
147+
148+
```neon
149+
services:
150+
- FileLogger( log.txt )
151+
- SendMailMailer
152+
- NewsletterManager
153+
```
154+
155+
The big advantage is the shortness of configuration.
156+
157+
Nette DI actually generates PHP code of container. Therefore it is extremely fast. Developer can see the code, so he knows exactly what it is doing. He can even trace it.
158+
159+
Usage of Nette DI is very easy. In first, install it using Composer:
160+
161+
```
162+
composer require nette/di
163+
```
164+
165+
Save the (above) configuration to the file `config.neon` and let's create a container:
166+
167+
```php
168+
$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp');
169+
$class = $loader->load('', function($compiler) {
170+
$compiler->loadConfig(__DIR__ . '/config.neon');
171+
});
172+
$container = new $class;
173+
```
174+
175+
and then use container to create object `NewsletterManager` and we can send e-mails:
45176

46177
```php
47-
$article = $container->getService('article');
178+
$manager = $container->getByType('NewsletterManager');
179+
$manager->distribute(['[email protected]', ...]);
48180
```
49181

50-
As have been said, all services are created in one container only once, but it would be more useful, if the container was creating always a new instance of `Article`. It could be achieved easily: Instead of the factory for the service `article` we'll create an ordinary method `createArticle`:
182+
The container will be generated only once and the code is stored in cache (in directory `__DIR__ . '/temp'`). Therefore the loading of configuration file is placed in the closure in `$loader->load()`, so it is called only once.
183+
184+
During development it is useful to activate auto-refresh mode which automatically regenerate the container when any class or configuration file is changed. Just in the constructor `ContainerLoader` append `TRUE` as the second argument:
51185

52186
```php
53-
class MyContainer extends Nette\DI\Container
187+
$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', TRUE);
188+
```
189+
190+
191+
Services
192+
--------
193+
194+
Services are registered in the DI container and their dependencies are automatically passed.
195+
196+
```neon
197+
services:
198+
service1: App\Service1
199+
```
200+
201+
All dependencies declared in the constructor of this service will be automatically passed:
202+
203+
```php
204+
namespace App;
205+
206+
class Service1
54207
{
208+
private $anotherService;
55209

56-
function createServiceConnection()
210+
public function __construct(AnotherService $service)
57211
{
58-
return new Nette\Database\Connection(
59-
$this->parameters['dsn'],
60-
$this->parameters['user'],
61-
$this->parameters['password']
62-
);
212+
$this->anotherService = $service;
63213
}
214+
}
215+
```
216+
217+
Constructor passing is the preferred way of dependency injection for services.
218+
219+
If we want to pass dependencies by the setter, we can add the `setup` section to the service definition:
220+
221+
```neon
222+
services:
223+
service2:
224+
class: App\Service2
225+
setup:
226+
- setAnotherService
227+
```
228+
229+
Class of the service:
230+
231+
```php
232+
namespace App;
64233

65-
function createArticle()
234+
class Service2
235+
{
236+
private $anotherService;
237+
238+
public function setAnotherService(AnotherService $service)
66239
{
67-
return new Article($this->connection);
240+
$this->anotherService = $service;
68241
}
242+
}
243+
```
244+
245+
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:
69246

247+
```neon
248+
services:
249+
service3:
250+
class: App\Service3
251+
inject: yes
252+
```
253+
254+
Dependency `Service1` will be passed by calling the `inject*` method, dependency `Service2` will be assigned to the `$service2` variable:
255+
256+
```php
257+
namespace App;
258+
259+
class Service3
260+
{
261+
// 1) inject* method:
262+
private $service1;
263+
264+
public function injectService1(Service1 $service)
265+
{
266+
$this->service1 = $service1;
267+
}
268+
269+
// 2) Assign to the variable with the @inject annotation:
270+
/** @inject @var \App\Service2 */
271+
public $service2;
70272
}
273+
```
274+
275+
However, this method is not ideal, 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.
71276

72-
$container = new MyContainer(...);
73277

74-
$article = $container->createArticle();
278+
279+
Component Factory
280+
-----------------
281+
282+
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.
283+
284+
The interface must have exactly one method named `create`. Our component factory interface could be declared in the following way:
285+
286+
```php
287+
interface ITableFactory
288+
{
289+
/**
290+
* @return Table
291+
*/
292+
public function create();
293+
}
294+
```
295+
296+
The `create` method will instantiate an `Table` component with the following definition:
297+
298+
```php
299+
class Table
300+
{
301+
private $manager;
302+
303+
public function __construct(Manager $Manager)
304+
{
305+
$this->Manager = $manager;
306+
}
307+
}
308+
```
309+
310+
The factory will be registered in the `config.neon` file:
311+
312+
```neon
313+
services:
314+
- ITableFactory
315+
```
316+
317+
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:
318+
319+
```neon
320+
services:
321+
tableFactory:
322+
implement: ITableFactory
75323
```
76324

77-
From the call of `$container->createArticle()` is evident, that a new object is always created. It is then a programmer's convention.
325+
This full definition allows us to declare additional configuration of the component using the `arguments` and `setup` sections, similarly as for all other services.
326+
327+
In our code, we only have to obtain the factory instance and call the `create` method:
328+
329+
```php
330+
class Foo
331+
{
332+
private $tableFactory;
333+
334+
function __construct(ITableFactory $tableFactory)
335+
{
336+
$this->tableFactory = $tableFactory;
337+
}
338+
339+
function bar()
340+
{
341+
$table = $this->tableFactory->create();
342+
}
343+
}
344+
```

0 commit comments

Comments
 (0)