@@ -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,46 +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
- .. caution ::
220
-
221
- The service using the synchronized service will need to be public in order
222
- to have its setter called when the scope changes.
223
-
224
237
.. _changing-service-scope :
225
238
226
239
Changing the Scope of your Service
@@ -236,18 +249,18 @@ Changing the scope of a service should be done in its definition:
236
249
services :
237
250
greeting_card_manager :
238
251
class : Acme\HelloBundle\Mail\GreetingCardManager
239
- scope : request
240
- arguments : [@request ]
252
+ scope : client
253
+ arguments : [@client_configuration ]
241
254
242
255
.. code-block :: xml
243
256
244
257
<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
245
258
<services >
246
259
<service id =" greeting_card_manager"
247
260
class =" Acme\HelloBundle\Mail\GreetingCardManager"
248
- scope =" request "
261
+ scope =" client "
249
262
/>
250
- <argument type =" service" id =" request " />
263
+ <argument type =" service" id =" client_configuration " />
251
264
</services >
252
265
253
266
.. code-block :: php
@@ -259,9 +272,9 @@ Changing the scope of a service should be done in its definition:
259
272
'greeting_card_manager',
260
273
new Definition(
261
274
'Acme\HelloBundle\Mail\GreetingCardManager',
262
- array(new Reference('request '),
275
+ array(new Reference('client_configuration '),
263
276
))
264
- )->setScope('request ');
277
+ )->setScope('client ');
265
278
266
279
.. _passing-container :
267
280
@@ -289,15 +302,15 @@ into your service::
289
302
290
303
public function sendEmail()
291
304
{
292
- $request = $this->container->get('request ');
293
- // ... do something using the request here
305
+ $request = $this->container->get('client_configuration ');
306
+ // ... do something using the client configuration here
294
307
}
295
308
}
296
309
297
310
.. caution ::
298
311
299
- Take care not to store the request in a property of the object for a
300
- 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
301
314
in the first section (except that Symfony cannot detect that you are
302
315
wrong).
303
316
0 commit comments