@@ -21,13 +21,15 @@ scopes:
21
21
22
22
- ``prototype ``: A new instance is created each time you request the service.
23
23
24
- The FrameworkBundle also defines a third scope: ``request ``. This scope is
25
- tied to the request, meaning a new instance is created for each subrequest
26
- and is unavailable outside the request (for instance in the CLI).
24
+ The
25
+ :class: `Symfony\\ Component\\ HttpKernel\\ DependencyInjection\\ ContainerAwareHttpKernel `
26
+ also defines a third scope: ``request ``. This scope is tied to the request,
27
+ meaning a new instance is created for each subrequest and is unavailable
28
+ outside the request (for instance in the CLI).
27
29
28
30
Scopes add a constraint on the dependencies of a service: a service cannot
29
31
depend on services from a narrower scope. For example, if you create a generic
30
- ``my_foo `` service, but try to inject the ``request `` component , you'll receive
32
+ ``my_foo `` service, but try to inject the ``request `` service , you will receive
31
33
a :class: `Symfony\\ Component\\ DependencyInjection\\ Exception\\ ScopeWideningInjectionException `
32
34
when compiling the container. Read the sidebar below for more details.
33
35
@@ -69,10 +71,71 @@ when compiling the container. Read the sidebar below for more details.
69
71
A service can of course depend on a service from a wider scope without
70
72
any issue.
71
73
72
- Setting the Scope in the Definition
73
- -----------------------------------
74
+ Using a Service from a narrower Scope
75
+ -------------------------------------
76
+
77
+ If your service has a dependency on a scoped service (like the ``request ``),
78
+ you have three ways to deal with it:
79
+
80
+ * Use setter injection if the dependency is "synchronized"; this is the
81
+ recommended way and the best solution for the ``request `` instance as it is
82
+ synchronized with the ``request `` scope (see
83
+ :ref: `using-synchronized-service `).
84
+
85
+ * Put your service in the same scope as the dependency (or a narrower one). If
86
+ you depend on the ``request `` service, this means putting your new service
87
+ in the ``request `` scope (see :ref: `changing-service-scope `);
88
+
89
+ * Pass the entire container to your service and retrieve your dependency from
90
+ the container each time you need it to be sure you have the right instance
91
+ -- your service can live in the default ``container `` scope (see
92
+ :ref: `passing-container `);
93
+
94
+ Each scenario is detailed in the following sections.
95
+
96
+ .. _using-synchronized-service :
97
+
98
+ Using a synchronized Service
99
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
74
100
75
- The scope of a service is set in the definition of the service:
101
+ Injecting the container or setting your service to a narrower scope have
102
+ drawbacks. For synchronized services (like the ``request ``), using setter
103
+ injection is the best option as it has no drawbacks and everything works
104
+ without any special code in your service or in your definition::
105
+
106
+ // src/Acme/HelloBundle/Mail/Mailer.php
107
+ namespace Acme\HelloBundle\Mail;
108
+
109
+ use Symfony\Component\HttpFoundation\Request;
110
+
111
+ class Mailer
112
+ {
113
+ protected $request;
114
+
115
+ public function setRequest(Request $request = null)
116
+ {
117
+ $this->request = $request;
118
+ }
119
+
120
+ public function sendEmail()
121
+ {
122
+ if (null === $this->request) {
123
+ // throw an error?
124
+ }
125
+
126
+ // ... do something using the request here
127
+ }
128
+ }
129
+
130
+ Whenever the ``request `` is entered or leaved, the service container will
131
+ automatically call the ``setRequest() `` method with the current ``request ``
132
+ instance.
133
+
134
+ You might have noticed that the ``setRequest() `` method accepts ``null `` as a
135
+ valid value for the ``request `` argument. That's because when leaving the
136
+ ``request `` scope, the ``request `` instance can be ``null `` (for the master
137
+ request for instance). Of course, you should take care of this possibility in
138
+ your code. This should also be taken into account when declaring your service:
76
139
77
140
.. configuration-block ::
78
141
@@ -82,42 +145,117 @@ The scope of a service is set in the definition of the service:
82
145
services :
83
146
greeting_card_manager :
84
147
class : Acme\HelloBundle\Mail\GreetingCardManager
85
- scope : request
148
+ calls :
149
+ - [setRequest, ['@?request']]
86
150
87
151
.. code-block :: xml
88
152
89
153
<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
90
154
<services >
91
- <service id =" greeting_card_manager" class =" Acme\HelloBundle\Mail\GreetingCardManager" scope =" request" />
155
+ <service id =" greeting_card_manager"
156
+ class =" Acme\HelloBundle\Mail\GreetingCardManager"
157
+ />
158
+ <call method =" setRequest" >
159
+ <argument type =" service" id =" request" on-invalid =" null" strict =" false" />
160
+ </call >
92
161
</services >
93
162
94
163
.. code-block :: php
95
164
96
165
// src/Acme/HelloBundle/Resources/config/services.php
97
166
use Symfony\Component\DependencyInjection\Definition;
167
+ use Symfony\Component\DependencyInjection\ContainerInterface;
98
168
99
- $container->setDefinition(
169
+ $definition = $ container->setDefinition(
100
170
'greeting_card_manager',
101
171
new Definition('Acme\HelloBundle\Mail\GreetingCardManager')
102
- )->setScope('request');
172
+ )
173
+ ->addMethodCall('setRequest', array(
174
+ new Reference('request', ContainerInterface::NULL_ON_INVALID_REFERENCE, false)
175
+ ));
103
176
104
- If you don't specify the scope, it defaults to ``container ``, which is what
105
- you want most of the time. Unless your service depends on another service
106
- that's scoped to a narrower scope (most commonly, the ``request `` service),
107
- you probably don't need to set the scope.
177
+ .. tip ::
108
178
109
- Using a Service from a narrower Scope
110
- -------------------------------------
179
+ You can declare your own ``synchronized `` services very easily; here is
180
+ the declaration of the ``request `` service for reference:
181
+
182
+ .. configuration-block ::
183
+
184
+ .. code-block :: yaml
111
185
112
- If your service depends on a scoped service, the best solution is to put
113
- it in the same scope (or a narrower one). Usually, this means putting your
114
- new service in the ``request `` scope.
186
+ services :
187
+ request :
188
+ scope : request
189
+ synthetic : true
190
+ synchronized : true
115
191
116
- But this is not always possible (for instance, a twig extension must be in
117
- the ``container `` scope as the Twig environment needs it as a dependency).
118
- In these cases, you should pass the entire container into your service and
119
- retrieve your dependency from the container each time you need it to be sure
120
- you have the right instance::
192
+ .. code-block :: xml
193
+
194
+ <services >
195
+ <service id =" request" scope =" request" synthetic =" true" synchronized =" true" />
196
+ </services >
197
+
198
+ .. code-block :: php
199
+
200
+ use Symfony\Component\DependencyInjection\Definition;
201
+ use Symfony\Component\DependencyInjection\ContainerInterface;
202
+
203
+ $definition = $container->setDefinition('request')
204
+ ->setScope('request')
205
+ ->setSynthetic(true)
206
+ ->setSynchronized(true);
207
+
208
+ .. _changing-service-scope :
209
+
210
+ Changing the Scope of your Service
211
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
212
+
213
+ Changing the scope of a service should be done set in its definition:
214
+
215
+ .. configuration-block ::
216
+
217
+ .. code-block :: yaml
218
+
219
+ # src/Acme/HelloBundle/Resources/config/services.yml
220
+ services :
221
+ greeting_card_manager :
222
+ class : Acme\HelloBundle\Mail\GreetingCardManager
223
+ scope : request
224
+ arguments : [@request]
225
+
226
+ .. code-block :: xml
227
+
228
+ <!-- src/Acme/HelloBundle/Resources/config/services.xml -->
229
+ <services >
230
+ <service id =" greeting_card_manager"
231
+ class =" Acme\HelloBundle\Mail\GreetingCardManager"
232
+ scope =" request"
233
+ />
234
+ <argument type =" service" id =" request" />
235
+ </services >
236
+
237
+ .. code-block :: php
238
+
239
+ // src/Acme/HelloBundle/Resources/config/services.php
240
+ use Symfony\Component\DependencyInjection\Definition;
241
+
242
+ $definition = $container->setDefinition(
243
+ 'greeting_card_manager',
244
+ new Definition(
245
+ 'Acme\HelloBundle\Mail\GreetingCardManager',
246
+ array(new Reference('request'),
247
+ ))
248
+ )->setScope('request');
249
+
250
+ .. _passing-container :
251
+
252
+ Passing the Container as a Dependency of your Service
253
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
254
+
255
+ Setting the scope to a narrower one is not always possible (for instance, a
256
+ twig extension must be in the ``container `` scope as the Twig environment
257
+ needs it as a dependency). In these cases, you can pass the entire container
258
+ into your service::
121
259
122
260
// src/Acme/HelloBundle/Mail/Mailer.php
123
261
namespace Acme\HelloBundle\Mail;
@@ -160,8 +298,7 @@ The service config for this class would look something like this:
160
298
services :
161
299
my_mailer :
162
300
class : " %my_mailer.class%"
163
- arguments :
164
- - " @service_container"
301
+ arguments : ["@service_container"]
165
302
# scope: container can be omitted as it is the default
166
303
167
304
.. code-block :: xml
@@ -195,10 +332,11 @@ The service config for this class would look something like this:
195
332
.. note ::
196
333
197
334
Injecting the whole container into a service is generally not a good
198
- idea (only inject what you need). In some rare cases, it's necessary
199
- when you have a service in the `` container `` scope that needs a service
200
- in the `` request `` scope.
335
+ idea (only inject what you need).
336
+
337
+ .. tip ::
201
338
202
- If you define a controller as a service then you can get the ``Request `` object
203
- without injecting the container by having it passed in as an argument of your
204
- action method. See :ref: `book-controller-request-argument ` for details.
339
+ If you define a controller as a service then you can get the ``Request ``
340
+ object without injecting the container by having it passed in as an
341
+ argument of your action method. See
342
+ :ref: `book-controller-request-argument ` for details.
0 commit comments