@@ -12,7 +12,9 @@ This entry is all about scopes, a somewhat advanced topic related to the
12
12
13
13
If you are trying to inject the ``request `` service, the simple solution
14
14
is to inject the ``request_stack `` service instead and access the current
15
- Request by calling the ``getCurrentRequest() `` method.
15
+ Request by calling the ``getCurrentRequest() `` method. The rest of this
16
+ entry talks about scopes in a theoretical and more advanced way. If you're
17
+ dealing with scopes for the ``request `` service, simply inject ``request_stack ``.
16
18
17
19
Understanding Scopes
18
20
--------------------
@@ -32,10 +34,22 @@ also defines a third scope: ``request``. This scope is tied to the request,
32
34
meaning a new instance is created for each subrequest and is unavailable
33
35
outside the request (for instance in the CLI).
34
36
37
+ The Example: client Scope
38
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
39
+
40
+ Other than the ``request `` service (which has a simple solution, see the
41
+ above note), no services in the default Symfony2 container belong to any
42
+ scope other than ``container `` and ``prototype ``. But for the purposes of
43
+ this entry, imagine there is another scope ``client `` and a service ``client_configuration ``
44
+ that belongs to it. This is not a common situation, but the idea is that
45
+ you may enter and exit multiple ``client `` scopes during a request, and each
46
+ has its own ``client_configuration `` service.
47
+
35
48
Scopes add a constraint on the dependencies of a service: a service cannot
36
49
depend on services from a narrower scope. For example, if you create a generic
37
- ``my_foo `` service, but try to inject the ``request `` service, you will receive
38
- a :class: `Symfony\\ Component\\ DependencyInjection\\ Exception\\ ScopeWideningInjectionException `
50
+ ``my_foo `` service, but try to inject the ``client_configuration `` service,
51
+ you will receive a
52
+ :class: `Symfony\\ Component\\ DependencyInjection\\ Exception\\ ScopeWideningInjectionException `
39
53
when compiling the container. Read the sidebar below for more details.
40
54
41
55
.. sidebar :: Scopes and Dependencies
@@ -45,28 +59,29 @@ when compiling the container. Read the sidebar below for more details.
45
59
every time you ask the container for the ``my_mailer `` service, you get
46
60
the same object back. This is usually how you want your services to work.
47
61
48
- Imagine, however, that you need the ``request `` service in your `` my_mailer ``
49
- service, maybe because you're reading the URL of the current request.
50
- So, you add it as a constructor argument. Let's look at why this presents
51
- a problem:
62
+ Imagine, however, that you need the ``client_configuration `` service
63
+ in your `` my_mailer `` service, maybe because you're reading some details
64
+ from it, such as what the "sender" address should be. You add it as a
65
+ constructor argument. Let's look at why this presents a problem:
52
66
53
67
* When requesting ``my_mailer ``, an instance of ``my_mailer `` (let's call
54
- it *MailerA *) is created and the ``request `` service (let's call it
55
- * RequestA *) is passed to it. Life is good!
68
+ it *MailerA *) is created and the ``client_configuration `` service (let's
69
+ call it * ConfigurationA *) is passed to it. Life is good!
56
70
57
- * You've now made a subrequest in Symfony, which is a fancy way of saying
58
- that you've called, for example, the `` {{ render(...) }} `` Twig function,
59
- which executes another controller. Internally, the old `` request `` service
60
- (* RequestA *) is actually replaced by a new request instance (* RequestB *).
61
- This happens in the background, and it's totally normal .
71
+ * Your application now needs to do something with another client, and
72
+ you've architected your application in such a way that you handle this
73
+ by entering a new `` client_configuration `` scope and setting a new
74
+ `` client_configuration `` service into the container. Let's call this
75
+ * ConfigurationB * .
62
76
63
- * In your embedded controller , you once again ask for the ``my_mailer ``
77
+ * Somewhere in your application , you once again ask for the ``my_mailer ``
64
78
service. Since your service is in the ``container `` scope, the same
65
79
instance (*MailerA *) is just re-used. But here's the problem: the
66
- *MailerA * instance still contains the old *RequestA * object, which
67
- is now **not ** the correct request object to have (*RequestB * is now
68
- the current ``request `` service). This is subtle, but the mis-match could
69
- cause major problems, which is why it's not allowed.
80
+ *MailerA * instance still contains the old *ConfigurationA * object, which
81
+ is now **not ** the correct configuration object to have (*ConfigurationB *
82
+ is now the current ``client_configuration `` service). This is subtle,
83
+ but the mis-match could cause major problems, which is why it's not
84
+ allowed.
70
85
71
86
So, that's the reason *why * scopes exist, and how they can cause
72
87
problems. Keep reading to find out the common solutions.
@@ -79,19 +94,14 @@ when compiling the container. Read the sidebar below for more details.
79
94
Using a Service from a narrower Scope
80
95
-------------------------------------
81
96
82
- The most common problem with "scope" is when your service has a dependency
83
- on the ``request `` service. The *easiest * way to solve this is to instead
84
- inject the ``request_stack `` service and access the current Request by calling
85
- the ``getCurrentRequest() `` method.
86
-
87
- This solution is great, but there are also others:
97
+ There are several solutions to the scope problem:
88
98
89
99
* Use setter injection if the dependency is "synchronized"; (see
90
100
:ref: `using-synchronized-service `).
91
101
92
102
* Put your service in the same scope as the dependency (or a narrower one). If
93
- you depend on the ``request `` service, this means putting your new service
94
- in the ``request `` scope (see :ref: `changing-service-scope `);
103
+ you depend on the ``client_configuration `` service, this means putting your
104
+ new service in the ``client `` scope (see :ref: `changing-service-scope `);
95
105
96
106
* Pass the entire container to your service and retrieve your dependency from
97
107
the container each time you need it to be sure you have the right instance
@@ -109,42 +119,81 @@ Using a synchronized Service
109
119
Synchronized services are new in Symfony 2.3.
110
120
111
121
Injecting the container or setting your service to a narrower scope have
112
- drawbacks. For synchronized services (like the ``request ``), using setter
113
- injection is a nice option as it has no drawbacks and everything works
114
- without any special code in your service or in your definition::
122
+ drawbacks. Assume first that the ``client_configuration `` service has been
123
+ marked as "synchronized":
124
+
125
+ .. configuration-block ::
126
+
127
+ .. code-block :: yaml
128
+
129
+ # app/config/config.yml
130
+ services :
131
+ client_configuration :
132
+ class : Acme\HelloBundle\Client\ClientConfiguration
133
+ scope : client
134
+ synchronized : true
135
+
136
+ .. code-block :: xml
137
+
138
+ <!-- app/config/config.xml -->
139
+ <?xml version =" 1.0" encoding =" UTF-8" ?>
140
+ <container xmlns =" http://symfony.com/schema/dic/services"
141
+ xmlns : xsi =" http://www.w3.org/2001/XMLSchema-instance"
142
+ xsi : schemaLocation =" http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd" >
143
+
144
+ <services >
145
+ <service id =" client_configuration" scope =" client" synchronized =" true" class =" Acme\HelloBundle\Client\ClientConfiguration" />
146
+ </services >
147
+ </container >
148
+
149
+ .. code-block :: php
150
+
151
+ // app/config/config.php
152
+ use Symfony\Component\DependencyInjection\Definition;
153
+
154
+ $defn = new Definition(
155
+ 'Acme\HelloBundle\Client\ClientConfiguration',
156
+ array()
157
+ );
158
+ $defn->setScope('client');
159
+ $defn->setSynchronized(true);
160
+ $container->setDefinition('client_configuration', $defn);
161
+
162
+ Now, if you inject this service using setter injection, there are no drawbacks
163
+ and everything works without any special code in your service or in your definition::
115
164
116
165
// src/Acme/HelloBundle/Mail/Mailer.php
117
166
namespace Acme\HelloBundle\Mail;
118
167
119
- use Symfony\Component\HttpFoundation\Request ;
168
+ use Acme\HelloBundle\Client\ClientConfiguration ;
120
169
121
170
class Mailer
122
171
{
123
- protected $request ;
172
+ protected $clientConfiguration ;
124
173
125
- public function setRequest(Request $request = null)
174
+ public function setClientConfiguration(ClientConfiguration $clientConfiguration = null)
126
175
{
127
- $this->request = $request ;
176
+ $this->clientConfiguration = $clientConfiguration ;
128
177
}
129
178
130
179
public function sendEmail()
131
180
{
132
- if (null === $this->request ) {
181
+ if (null === $this->clientConfiguration ) {
133
182
// throw an error?
134
183
}
135
184
136
- // ... do something using the request here
185
+ // ... do something using the client configuration here
137
186
}
138
187
}
139
188
140
- Whenever the ``request `` scope is entered or left, the service container will
141
- automatically call the ``setRequest () `` method with the current `` request ``
142
- instance.
189
+ Whenever the ``client `` scope is entered or left, the service container will
190
+ automatically call the ``setClientConfiguration () `` method with the current
191
+ `` client_configuration `` instance.
143
192
144
- You might have noticed that the ``setRequest () `` method accepts `` null `` as a
145
- valid value for the ``request `` argument. That's because when leaving the
146
- `` request `` scope, the ``request `` instance can be `` null `` (for the master
147
- request for instance) . Of course, you should take care of this possibility in
193
+ You might have noticed that the ``setClientConfiguration () `` method accepts
194
+ `` null `` as a valid value for the ``client_configuration `` argument. That's
195
+ because when leaving the ``client `` scope, the `` client_configuration `` instance
196
+ can be `` null `` . Of course, you should take care of this possibility in
148
197
your code. This should also be taken into account when declaring your service:
149
198
150
199
.. configuration-block ::
@@ -156,7 +205,7 @@ your code. This should also be taken into account when declaring your service:
156
205
greeting_card_manager :
157
206
class : Acme\HelloBundle\Mail\GreetingCardManager
158
207
calls :
159
- - [setRequest , ['@?request =']]
208
+ - [setClientConfiguration , ['@?client_configuration =']]
160
209
161
210
.. code-block :: xml
162
211
@@ -165,8 +214,8 @@ your code. This should also be taken into account when declaring your service:
165
214
<service id =" greeting_card_manager"
166
215
class =" Acme\HelloBundle\Mail\GreetingCardManager"
167
216
>
168
- <call method =" setRequest " >
169
- <argument type =" service" id =" request " on-invalid =" null" strict =" false" />
217
+ <call method =" setClientConfiguration " >
218
+ <argument type =" service" id =" client_configuration " on-invalid =" null" strict =" false" />
170
219
</call >
171
220
</service >
172
221
</services >
@@ -181,41 +230,10 @@ your code. This should also be taken into account when declaring your service:
181
230
'greeting_card_manager',
182
231
new Definition('Acme\HelloBundle\Mail\GreetingCardManager')
183
232
)
184
- ->addMethodCall('setRequest ', array(
185
- new Reference('request ', ContainerInterface::NULL_ON_INVALID_REFERENCE, false)
233
+ ->addMethodCall('setClientConfiguration ', array(
234
+ new Reference('client_configuration ', ContainerInterface::NULL_ON_INVALID_REFERENCE, false)
186
235
));
187
236
188
- .. tip ::
189
-
190
- You can declare your own ``synchronized `` services very easily; here is
191
- the declaration of the ``request `` service for reference:
192
-
193
- .. configuration-block ::
194
-
195
- .. code-block :: yaml
196
-
197
- services :
198
- request :
199
- scope : request
200
- synthetic : true
201
- synchronized : true
202
-
203
- .. code-block :: xml
204
-
205
- <services >
206
- <service id =" request" scope =" request" synthetic =" true" synchronized =" true" />
207
- </services >
208
-
209
- .. code-block :: php
210
-
211
- use Symfony\Component\DependencyInjection\Definition;
212
- use Symfony\Component\DependencyInjection\ContainerInterface;
213
-
214
- $definition = $container->setDefinition('request')
215
- ->setScope('request')
216
- ->setSynthetic(true)
217
- ->setSynchronized(true);
218
-
219
237
.. _changing-service-scope :
220
238
221
239
Changing the Scope of your Service
@@ -231,18 +249,18 @@ Changing the scope of a service should be done in its definition:
231
249
services :
232
250
greeting_card_manager :
233
251
class : Acme\HelloBundle\Mail\GreetingCardManager
234
- scope : request
235
- arguments : [@request ]
252
+ scope : client
253
+ arguments : [@client_configuration ]
236
254
237
255
.. code-block :: xml
238
256
239
257
<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
240
258
<services >
241
259
<service id =" greeting_card_manager"
242
260
class =" Acme\HelloBundle\Mail\GreetingCardManager"
243
- scope =" request "
261
+ scope =" client "
244
262
/>
245
- <argument type =" service" id =" request " />
263
+ <argument type =" service" id =" client_configuration " />
246
264
</services >
247
265
248
266
.. code-block :: php
@@ -254,9 +272,9 @@ Changing the scope of a service should be done in its definition:
254
272
'greeting_card_manager',
255
273
new Definition(
256
274
'Acme\HelloBundle\Mail\GreetingCardManager',
257
- array(new Reference('request '),
275
+ array(new Reference('client_configuration '),
258
276
))
259
- )->setScope('request ');
277
+ )->setScope('client ');
260
278
261
279
.. _passing-container :
262
280
@@ -284,15 +302,15 @@ into your service::
284
302
285
303
public function sendEmail()
286
304
{
287
- $request = $this->container->get('request ');
288
- // ... do something using the request here
305
+ $request = $this->container->get('client_configuration ');
306
+ // ... do something using the client configuration here
289
307
}
290
308
}
291
309
292
310
.. caution ::
293
311
294
- Take care not to store the request in a property of the object for a
295
- future call of the service as it would cause the same issue described
312
+ Take care not to store the client configuration in a property of the object
313
+ for a future call of the service as it would cause the same issue described
296
314
in the first section (except that Symfony cannot detect that you are
297
315
wrong).
298
316
0 commit comments