Skip to content

Commit 2821a55

Browse files
committed
Merge pull request #2343 from fabpot/synchronized-services
updated documentation for synchronized services
2 parents 7b06db8 + f9bbfcd commit 2821a55

File tree

1 file changed

+171
-33
lines changed

1 file changed

+171
-33
lines changed

cookbook/service_container/scopes.rst

+171-33
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ scopes:
2121

2222
- ``prototype``: A new instance is created each time you request the service.
2323

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).
2729

2830
Scopes add a constraint on the dependencies of a service: a service cannot
2931
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
3133
a :class:`Symfony\\Component\\DependencyInjection\\Exception\\ScopeWideningInjectionException`
3234
when compiling the container. Read the sidebar below for more details.
3335

@@ -69,10 +71,71 @@ when compiling the container. Read the sidebar below for more details.
6971
A service can of course depend on a service from a wider scope without
7072
any issue.
7173

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+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
74100

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

77140
.. configuration-block::
78141

@@ -82,42 +145,117 @@ The scope of a service is set in the definition of the service:
82145
services:
83146
greeting_card_manager:
84147
class: Acme\HelloBundle\Mail\GreetingCardManager
85-
scope: request
148+
calls:
149+
- [setRequest, ['@?request']]
86150
87151
.. code-block:: xml
88152
89153
<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
90154
<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>
92161
</services>
93162
94163
.. code-block:: php
95164
96165
// src/Acme/HelloBundle/Resources/config/services.php
97166
use Symfony\Component\DependencyInjection\Definition;
167+
use Symfony\Component\DependencyInjection\ContainerInterface;
98168
99-
$container->setDefinition(
169+
$definition = $container->setDefinition(
100170
'greeting_card_manager',
101171
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+
));
103176
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::
108178

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
111185
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
115191
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::
121259

122260
// src/Acme/HelloBundle/Mail/Mailer.php
123261
namespace Acme\HelloBundle\Mail;
@@ -160,8 +298,7 @@ The service config for this class would look something like this:
160298
services:
161299
my_mailer:
162300
class: "%my_mailer.class%"
163-
arguments:
164-
- "@service_container"
301+
arguments: ["@service_container"]
165302
# scope: container can be omitted as it is the default
166303
167304
.. code-block:: xml
@@ -195,10 +332,11 @@ The service config for this class would look something like this:
195332
.. note::
196333

197334
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::
201338

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

Comments
 (0)