Skip to content

Commit 9ea7a27

Browse files
committed
[cookbook][service_container] Added an entry about scopes
1 parent 36999b3 commit 9ea7a27

File tree

3 files changed

+161
-0
lines changed

3 files changed

+161
-0
lines changed

cookbook/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Cookbook
3333
configuration/external_parameters
3434
service_container/factories
3535
service_container/parentservices
36+
service_container/scopes
3637
configuration/pdo_session_storage
3738

3839
bundles/best_practices

cookbook/map.rst.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
* :doc:`/cookbook/configuration/external_parameters`
4343
* :doc:`/cookbook/service_container/factories`
4444
* :doc:`/cookbook/service_container/parentservices`
45+
* :doc:`/cookbook/service_container/scopes`
4546
* :doc:`/cookbook/configuration/pdo_session_storage`
4647

4748
* **Bundles**

cookbook/service_container/scopes.rst

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
How to Work with scopes
2+
=======================
3+
4+
Understanding the Scopes
5+
------------------------
6+
7+
The scope of a service controls how long an instance of a service is used
8+
by the container. the Dependency Injection component provides the two generic
9+
scopes:
10+
11+
- `container` (the default one): The same instance is used each time you
12+
request it from this container.
13+
- `prototype`: A new instance is created each time you request the service.
14+
15+
FrameworkBundle defines the `request` scope between them. This scopes is
16+
tied to the request, so a new instance will be created for each subrequest
17+
and is unavailable outside the request (for instance in the CLI).
18+
19+
The scope adds a constraint on the dependencies of a service: a service cannot
20+
depend on services from a narrower scope. Using such a pattern will lead
21+
to a :class:Symfony\\Component\\DependencyInjection\\Exception\\ScopeWideningInjectionException`
22+
when compiling the container.
23+
24+
.. sidebar:: Understanding the constraint on the scope of dependencies
25+
26+
Let's imagine that your service A has a dependency to a service B from
27+
a narrower scope. Here is what occurs:
28+
29+
- When requesting A, an instance B1 is created for B and injected in A1.
30+
- When entering the new narrow scope (doing a subrequest for the `request`
31+
scope for instance), the container will need to create a B2 instance
32+
for the service B as B1 is now obsolete.
33+
- When requesting A, the container will reuse A1 (as it is still the
34+
good scope) which still contains the obsolete B1 instance.
35+
36+
.. note::
37+
38+
A service can of course depend on a service from a wider scope without
39+
any issue.
40+
41+
Setting the Scope in the Definition
42+
-----------------------------------
43+
44+
The scope of a service is defined in the definition of the service:
45+
46+
.. configuration-block::
47+
48+
.. code-block:: yaml
49+
50+
# src/Acme/HelloBundle/Resources/config/services.yml
51+
services:
52+
greeting_card_manager:
53+
class: Acme\HelloBundle\Mail\GreetingCardManager
54+
scope: request
55+
56+
.. code-block:: xml
57+
58+
<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
59+
<services>
60+
<service id="greeting_card_manager" class="Acme\HelloBundle\Mail\GreetingCardManager" scope="request" />
61+
</services>
62+
63+
.. code-block:: php
64+
65+
// src/Acme/HelloBundle/Resources/config/services.php
66+
use Symfony\Component\DependencyInjection\Definition;
67+
68+
$container->setDefinition(
69+
'greeting_card_manager',
70+
new Definition('Acme\HelloBundle\Mail\GreetingCardManager')
71+
)->setScope('request');
72+
73+
Using a Service from a narrower Scope
74+
-------------------------------------
75+
76+
If your service depends of a scoped service, the best solution is to put
77+
it in the same scope (or a narrower one). But this is not always possible
78+
(for instance, a twig extension must be in the `container` scope as the Twig
79+
environment needs it as a dependency).
80+
81+
Using a service from a narrower scope requires retrieving it from the container
82+
each time we need it to be sure to have the good instance.
83+
84+
namespace Acme\HelloBundle\Mail;
85+
86+
use Symfony\Component\DependencyInjection\ContainerInterface;
87+
88+
class Mailer
89+
{
90+
protected $container;
91+
92+
public function __construct(ContainerInterface $container)
93+
{
94+
$this->container = $container;
95+
}
96+
97+
public function sendEmail()
98+
{
99+
$request = $this->container->get('request');
100+
// Do something using the request here
101+
}
102+
}
103+
104+
.. warning::
105+
106+
Take care not to store the request in a property of the object for a
107+
future call of the service as it would be the same issue than described
108+
in the first section (except that symfony cannot detect that you are
109+
wrong).
110+
111+
The service config for this class would look something like this:
112+
113+
.. configuration-block::
114+
115+
.. code-block:: yaml
116+
117+
# src/Acme/HelloBundle/Resources/config/services.yml
118+
parameters:
119+
# ...
120+
my_mailer.class: Acme\HelloBundle\Mail\Mailer
121+
services:
122+
my_mailer:
123+
class: %my_mailer.class%
124+
arguments:
125+
- "@service_container"
126+
# scope: container can be omitted as it is the default
127+
128+
.. code-block:: xml
129+
130+
<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
131+
<parameters>
132+
<!-- ... -->
133+
<parameter key="my_mailer.class">Acme\HelloBundle\Mail\Mailer</parameter>
134+
</parameters>
135+
136+
<services>
137+
<service id="my_mailer" class="%my_mailer.class%">
138+
<argument type="service" id="service_container" />
139+
</service>
140+
</services>
141+
142+
.. code-block:: php
143+
144+
// src/Acme/HelloBundle/Resources/config/services.php
145+
use Symfony\Component\DependencyInjection\Definition;
146+
use Symfony\Component\DependencyInjection\Reference;
147+
148+
// ...
149+
$container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mail\Mailer');
150+
151+
$container->setDefinition('my_mailer', new Definition(
152+
'%my_mailer.class%',
153+
array(new Reference('service_container'))
154+
));
155+
156+
.. note::
157+
158+
Injecting the whole container in a service is generally a sign of an
159+
issue in the design but this is a valid use case.

0 commit comments

Comments
 (0)