Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b046e64

Browse files
committed
[#2956] Refactoring the scopes entry entirely to not talk about the request anymore, since it has a simpler solution
See sha: b84f52c for more information.
1 parent b84f52c commit b046e64

File tree

1 file changed

+107
-89
lines changed

1 file changed

+107
-89
lines changed

cookbook/service_container/scopes.rst

Lines changed: 107 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ This entry is all about scopes, a somewhat advanced topic related to the
1212

1313
If you are trying to inject the ``request`` service, the simple solution
1414
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``.
1618

1719
Understanding Scopes
1820
--------------------
@@ -32,10 +34,22 @@ also defines a third scope: ``request``. This scope is tied to the request,
3234
meaning a new instance is created for each subrequest and is unavailable
3335
outside the request (for instance in the CLI).
3436

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+
3548
Scopes add a constraint on the dependencies of a service: a service cannot
3649
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`
3953
when compiling the container. Read the sidebar below for more details.
4054

4155
.. sidebar:: Scopes and Dependencies
@@ -45,28 +59,29 @@ when compiling the container. Read the sidebar below for more details.
4559
every time you ask the container for the ``my_mailer`` service, you get
4660
the same object back. This is usually how you want your services to work.
4761

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:
5266

5367
* 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!
5670

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*.
6276

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``
6478
service. Since your service is in the ``container`` scope, the same
6579
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.
7085

7186
So, that's the reason *why* scopes exist, and how they can cause
7287
problems. Keep reading to find out the common solutions.
@@ -79,19 +94,14 @@ when compiling the container. Read the sidebar below for more details.
7994
Using a Service from a narrower Scope
8095
-------------------------------------
8196

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:
8898

8999
* Use setter injection if the dependency is "synchronized"; (see
90100
:ref:`using-synchronized-service`).
91101

92102
* 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`);
95105

96106
* Pass the entire container to your service and retrieve your dependency from
97107
the container each time you need it to be sure you have the right instance
@@ -109,42 +119,81 @@ Using a synchronized Service
109119
Synchronized services are new in Symfony 2.3.
110120

111121
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::
115164

116165
// src/Acme/HelloBundle/Mail/Mailer.php
117166
namespace Acme\HelloBundle\Mail;
118167

119-
use Symfony\Component\HttpFoundation\Request;
168+
use Acme\HelloBundle\Client\ClientConfiguration;
120169

121170
class Mailer
122171
{
123-
protected $request;
172+
protected $clientConfiguration;
124173

125-
public function setRequest(Request $request = null)
174+
public function setClientConfiguration(ClientConfiguration $clientConfiguration = null)
126175
{
127-
$this->request = $request;
176+
$this->clientConfiguration = $clientConfiguration;
128177
}
129178

130179
public function sendEmail()
131180
{
132-
if (null === $this->request) {
181+
if (null === $this->clientConfiguration) {
133182
// throw an error?
134183
}
135184

136-
// ... do something using the request here
185+
// ... do something using the client configuration here
137186
}
138187
}
139188

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.
143192

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
148197
your code. This should also be taken into account when declaring your service:
149198

150199
.. configuration-block::
@@ -156,7 +205,7 @@ your code. This should also be taken into account when declaring your service:
156205
greeting_card_manager:
157206
class: Acme\HelloBundle\Mail\GreetingCardManager
158207
calls:
159-
- [setRequest, ['@?request=']]
208+
- [setClientConfiguration, ['@?client_configuration=']]
160209
161210
.. code-block:: xml
162211
@@ -165,8 +214,8 @@ your code. This should also be taken into account when declaring your service:
165214
<service id="greeting_card_manager"
166215
class="Acme\HelloBundle\Mail\GreetingCardManager"
167216
>
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" />
170219
</call>
171220
</service>
172221
</services>
@@ -181,41 +230,10 @@ your code. This should also be taken into account when declaring your service:
181230
'greeting_card_manager',
182231
new Definition('Acme\HelloBundle\Mail\GreetingCardManager')
183232
)
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)
186235
));
187236
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-
219237
.. _changing-service-scope:
220238

221239
Changing the Scope of your Service
@@ -231,18 +249,18 @@ Changing the scope of a service should be done in its definition:
231249
services:
232250
greeting_card_manager:
233251
class: Acme\HelloBundle\Mail\GreetingCardManager
234-
scope: request
235-
arguments: [@request]
252+
scope: client
253+
arguments: [@client_configuration]
236254
237255
.. code-block:: xml
238256
239257
<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
240258
<services>
241259
<service id="greeting_card_manager"
242260
class="Acme\HelloBundle\Mail\GreetingCardManager"
243-
scope="request"
261+
scope="client"
244262
/>
245-
<argument type="service" id="request" />
263+
<argument type="service" id="client_configuration" />
246264
</services>
247265
248266
.. code-block:: php
@@ -254,9 +272,9 @@ Changing the scope of a service should be done in its definition:
254272
'greeting_card_manager',
255273
new Definition(
256274
'Acme\HelloBundle\Mail\GreetingCardManager',
257-
array(new Reference('request'),
275+
array(new Reference('client_configuration'),
258276
))
259-
)->setScope('request');
277+
)->setScope('client');
260278
261279
.. _passing-container:
262280

@@ -284,15 +302,15 @@ into your service::
284302

285303
public function sendEmail()
286304
{
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
289307
}
290308
}
291309

292310
.. caution::
293311

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
296314
in the first section (except that Symfony cannot detect that you are
297315
wrong).
298316

0 commit comments

Comments
 (0)