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
/
Copy pathdi-extensions.texy
189 lines (134 loc) · 7.15 KB
/
di-extensions.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
DI Container Extensions
***********************
`Configurator` does not generate the code itself, that is the task of [Nette\DI\Compiler|api:] and [Nette\DI\ContainerBuilder|api:] classes. First configuration files are loaded and passed to `Compiler`. Add your own extension to `Compiler` via `config.neon`:
/---code neon
extensions:
blog: MyBlogExtension
\---
Each `Compiler` extension must extend [Nette\DI\CompilerExtension|api:] and can implement three different methods that are called successively during the `Container` compilation.
CompilerExtension::loadConfiguration() .{toc: loadConfiguration()}
==================================================================
This method is called first and loads additional configuration files, creates methods using [Nette\DI\ContainerBuilder|api:] and most importantly processes the application's configuration.
Config can contain a section bearing the same name as your extension. Using the last example, following lines can appear in your config file:
/--code neon
blog: # same name as your extension
postsPerPage: 10
comments: FALSE
\--
Use [getConfig() |api:Nette\DI\CompilerExtension::getConfig()] method in the extension to list its configuration.
/--code php
class MyBlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$config = $this->getConfig();
// array(2) [ 'postsPerPage' => 10, 'comments' => FALSE ]
\--
Honoring the "convention over configuration":http://en.wikipedia.org/wiki/Convention_over_configuration principle we can set default values and be able to use the application without explicitly setting anything. First argument of `getConfig()` accepts an array of default values, into which the config section (as mentioned above) will be merged.
/--code php
class MyBlogExtension extends Nette\DI\CompilerExtension
{
public $defaults = array(
'postsPerPage' => 5,
'comments' => TRUE
);
public function loadConfiguration()
{
$config = $this->getConfig($this->defaults);
\--
Now we have the configuration in the `$config` array and can use it while creating the services. `ContainerBuilder` class allows you to use the same way of service description as NEON used in the configuration files.
/--code php
$builder = $this->getContainerBuilder();
\--
Create `blog_articles` service representing a model for handling and loading articles.
.[tip]
Convention is to prefix services by their name to avoid conflicts. Using `prefix()` method we can then access the service via `$container->getService('blog.articles`).
/--code php
$builder->addDefinition($this->prefix('articles'))
->setClass('MyBlog\ArticlesModel', array('@connection'));
\--
You can also create a component factory, pass it `blog_articles` service and set a number of posts per page option. How to use component factories will be shown soon.
/--code php
$builder->addDefinition($this->prefix('articlesList'))
->setClass('MyBlog\Components\ArticlesList', array($this->prefix('@articles')))
->addSetup('setPostsPerPage', $config['postsPerPage'])
->setShared(FALSE)->setAutowired(FALSE); // turns service into factory
\--
We will also need a `blog_comment` class and pass it connection to database and `blog_articles` instance. If the comments are disabled we can reflect it using `addSetup` method:
/--code php
$comments = $builder->addDefinition($this->prefix('comments'))
->setClass('MyBlog\CommentsModel', array('@connection', $this->prefix('@articles')));
if (!$config['comments']) { // optional disabling of commenting
$comments->addSetup('disableComments');
}
\--
Of course comments needs to be written and shown, so we add one more component factory - `blog_commentsControl`, that will allow us to list comments and to post new ones, unless they have been closed.
/--code php
$builder->addDefinition($this->prefix('commentsControl'))
->setClass('MyBlog\Components\CommentsControl', array($this->prefix('@comments')))
->setShared(FALSE)->setAutowired(FALSE); // turns service into factory
\--
.[note]
This division into models and components is purely illustrative.
Loading additional configurations
---------------------------------
If you prefer configuration files over extensions, you can move some of the definitions into separate configuration file.
/--code neon
services:
blog_articles:
class: MyBlog\ArticlesModel(@connection)
blog_comments:
class: MyBlog\CommentsModel(@connection, @blog_articles)
blog_articlesList:
class: MyBlog\Components\ArticlesList(@blog_articles)
blog_commentsControl:
class: MyBlog\Components\CommentsControl(@blog_comments)
\--
Load the file and set additional services
/--code php
public function loadConfiguration()
{
$config = $this->getConfig($this->defaults);
$builder = $this->getContainerBuilder();
// load additional config file for this extension
$this->compiler->parseServices($builder, $this->loadFromFile(__DIR__ . '/blog.neon'));
// set a number of articles per page in the component
$builder->getDefinition('blog_articlesList')
->addSetup('setPostsPerPage', $config['postsPerPage']);
// optional disabling of commenting
if (!$config['comments']) {
$builder->getDefinition('blog_comments')
->addSetup('disableComments');
}
}
\--
Thanks to NEON syntax you now have cleaner container extension.
CompilerExtension::beforeCompile() .{toc: beforeCompile()}
==========================================================
In `beforeCompile` phase we should not add any more services, however you can modify already existing ones or add relations between services (for example using tags).
CompilerExtension::afterCompile(Nette\PhpGenerator\ClassType $class) .{toc: afterCompile()}
=================================================================================================
In this phase the `Container` instance is already generated and contains all service methods and is ready to be stored into cache. Thanks to [Nette\PhpGenerator\ClassType |api:] you can add your own code to the container to modify the service generation.
To inspire yourself take a look at Nette Framework's `initialize` method, which Nette adds to process [some of the user settings |api:Nette\DI\Extensions\NetteExtension::afterCompile()]. This method is always called after instantiation of the container.
Here is a piece of code of `initialize` where Nette adds a session start and autorun of services marked with `run` tag.
/--code php
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$container = $this->getContainerBuilder();
$config = $this->getConfig($this->defaults);
// initialize method
$initialize = $class->methods['initialize'];
// automatic session start
if ($config['session']['autoStart']) {
$initialize->addBody('$this->session->start();');
}
// services with run tag must be run after instantition of the container
foreach ($container->findByTag('run') as $name => $foo) {
$initialize->addBody('$this->getService(?);', array($name));
}
}
\--
Methods' `beforeCompile()` and `afterCompile()` difference is that `beforeCompile()` has access to container and services while `afterCompile()` accesses its code (via "Nette\PhpGenerator":http://api.nette.org/2.0/namespace-Nette.PhpGenerator.html).
{{themeicon: icon-config.png}}
{{care: David Grudl|2}}
{{composer: nette/di}}