diff --git a/book/service_container.rst b/book/service_container.rst
index 2d3b65ab0c5..8644d93685a 100644
--- a/book/service_container.rst
+++ b/book/service_container.rst
@@ -754,28 +754,95 @@ Injecting the dependency by the setter method just needs a change of syntax:
and "setter injection". The Symfony2 service container also supports
"property injection".
+.. _book-container-request-stack:
+
Injecting the Request
~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 2.4
The ``request_stack`` service was introduced in version 2.4.
-Almost all Symfony2 built-in services behave in the same way: a single
-instance is created by the container which it returns whenever you get it or
-when it is injected into another service. There is one exception in a standard
-Symfony2 application: the ``request`` service.
-
-If you try to inject the ``request`` into a service, you will probably receive
-a
-:class:`Symfony\\Component\\DependencyInjection\\Exception\\ScopeWideningInjectionException`
-exception. That's because the ``request`` can **change** during the life-time
-of a container (when a sub-request is created for instance).
-
As of Symfony 2.4, instead of injecting the ``request`` service, you should
inject the ``request_stack`` service instead and access the Request by calling
-the ``getCurrentRequest()`` method. For earlier versions, or if you want to
-understand this problem better, refer to the cookbook article
-:doc:`/cookbook/service_container/scopes`.
+the ``getCurrentRequest()`` method:
+
+ namespace Acme\HelloBundle\Newsletter;
+
+ use Symfony\Component\HttpFoundation\RequestStack;
+
+ class NewsletterManager
+ {
+ protected $requestStack;
+
+ public function __construct(RequestStack $requestStack)
+ {
+ $this->requestStack = $requestStack;
+ }
+
+ public function anyMethod()
+ {
+ $request = $this->requestStack->getCurrentRequest();
+ // ... do something with the request
+ }
+
+ // ...
+ }
+
+Now, just inject the ``request_stack``, which behaves like any normal service:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # src/Acme/HelloBundle/Resources/config/services.yml
+ services:
+ newsletter_manager:
+ class: "Acme\HelloBundle\Newsletter\NewsletterManager"
+ arguments: ["@request_stack"]
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // src/Acme/HelloBundle/Resources/config/services.php
+ use Symfony\Component\DependencyInjection\Definition;
+ use Symfony\Component\DependencyInjection\Reference;
+
+ // ...
+ $container->setDefinition('newsletter_manager', new Definition(
+ 'Acme\HelloBundle\Newsletter\NewsletterManager',
+ array(new Reference('request_stack'))
+ ));
+
+.. sidebar: Why not Inject the request Service?
+
+ Almost all Symfony2 built-in services behave in the same way: a single
+ instance is created by the container which it returns whenever you get it or
+ when it is injected into another service. There is one exception in a standard
+ Symfony2 application: the ``request`` service.
+
+ If you try to inject the ``request`` into a service, you will probably receive
+ a
+ :class:`Symfony\\Component\\DependencyInjection\\Exception\\ScopeWideningInjectionException`
+ exception. That's because the ``request`` can **change** during the life-time
+ of a container (when a sub-request is created for instance).
+
.. tip::
diff --git a/cookbook/service_container/scopes.rst b/cookbook/service_container/scopes.rst
index 78ccb76126f..03d94e1a426 100644
--- a/cookbook/service_container/scopes.rst
+++ b/cookbook/service_container/scopes.rst
@@ -12,7 +12,10 @@ This entry is all about scopes, a somewhat advanced topic related to the
If you are trying to inject the ``request`` service, the simple solution
is to inject the ``request_stack`` service instead and access the current
- Request by calling the ``getCurrentRequest()`` method.
+ Request by calling the ``getCurrentRequest()`` method (see :ref:`book-container-request-stack`).
+ The rest of this entry talks about scopes in a theoretical and more advanced
+ way. If you're dealing with scopes for the ``request`` service, simply
+ inject ``request_stack``.
Understanding Scopes
--------------------
@@ -32,10 +35,22 @@ also defines a third scope: ``request``. This scope is tied to the request,
meaning a new instance is created for each subrequest and is unavailable
outside the request (for instance in the CLI).
+An Example: client Scope
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Other than the ``request`` service (which has a simple solution, see the
+above note), no services in the default Symfony2 container belong to any
+scope other than ``container`` and ``prototype``. But for the purposes of
+this entry, imagine there is another scope ``client`` and a service ``client_configuration``
+that belongs to it. This is not a common situation, but the idea is that
+you may enter and exit multiple ``client`` scopes during a request, and each
+has its own ``client_configuration`` service.
+
Scopes add a constraint on the dependencies of a service: a service cannot
depend on services from a narrower scope. For example, if you create a generic
-``my_foo`` service, but try to inject the ``request`` service, you will receive
-a :class:`Symfony\\Component\\DependencyInjection\\Exception\\ScopeWideningInjectionException`
+``my_foo`` service, but try to inject the ``client_configuration`` service,
+you will receive a
+:class:`Symfony\\Component\\DependencyInjection\\Exception\\ScopeWideningInjectionException`
when compiling the container. Read the sidebar below for more details.
.. sidebar:: Scopes and Dependencies
@@ -45,28 +60,29 @@ when compiling the container. Read the sidebar below for more details.
every time you ask the container for the ``my_mailer`` service, you get
the same object back. This is usually how you want your services to work.
- Imagine, however, that you need the ``request`` service in your ``my_mailer``
- service, maybe because you're reading the URL of the current request.
- So, you add it as a constructor argument. Let's look at why this presents
- a problem:
+ Imagine, however, that you need the ``client_configuration`` service
+ in your ``my_mailer`` service, maybe because you're reading some details
+ from it, such as what the "sender" address should be. You add it as a
+ constructor argument. Let's look at why this presents a problem:
- * When requesting ``my_mailer``, an instance of ``my_mailer`` (let's call
- it *MailerA*) is created and the ``request`` service (let's call it
- *RequestA*) is passed to it. Life is good!
+ * When requesting ``my_mailer``, an instance of ``my_mailer`` (called
+ *MailerA* here) is created and the ``client_configuration`` service (
+ called *ConfigurationA* here) is passed to it. Life is good!
- * You've now made a subrequest in Symfony, which is a fancy way of saying
- that you've called, for example, the ``{{ render(...) }}`` Twig function,
- which executes another controller. Internally, the old ``request`` service
- (*RequestA*) is actually replaced by a new request instance (*RequestB*).
- This happens in the background, and it's totally normal.
+ * Your application now needs to do something with another client, and
+ you've architected your application in such a way that you handle this
+ by entering a new ``client_configuration`` scope and setting a new
+ ``client_configuration`` service into the container. Call this
+ *ConfigurationB*.
- * In your embedded controller, you once again ask for the ``my_mailer``
+ * Somewhere in your application, you once again ask for the ``my_mailer``
service. Since your service is in the ``container`` scope, the same
instance (*MailerA*) is just re-used. But here's the problem: the
- *MailerA* instance still contains the old *RequestA* object, which
- is now **not** the correct request object to have (*RequestB* is now
- the current ``request`` service). This is subtle, but the mis-match could
- cause major problems, which is why it's not allowed.
+ *MailerA* instance still contains the old *ConfigurationA* object, which
+ is now **not** the correct configuration object to have (*ConfigurationB*
+ is now the current ``client_configuration`` service). This is subtle,
+ but the mis-match could cause major problems, which is why it's not
+ allowed.
So, that's the reason *why* scopes exist, and how they can cause
problems. Keep reading to find out the common solutions.
@@ -79,21 +95,16 @@ when compiling the container. Read the sidebar below for more details.
Using a Service from a narrower Scope
-------------------------------------
-The most common problem with "scope" is when your service has a dependency
-on the ``request`` service. The *easiest* way to solve this is to instead
-inject the ``request_stack`` service and access the current Request by calling
-the ``getCurrentRequest()`` method.
-
-This solution is great, but there are also others:
+There are several solutions to the scope problem:
-* Use setter injection if the dependency is "synchronized"; (see
+* A) Use setter injection if the dependency is "synchronized"; (see
:ref:`using-synchronized-service`).
-* Put your service in the same scope as the dependency (or a narrower one). If
- you depend on the ``request`` service, this means putting your new service
- in the ``request`` scope (see :ref:`changing-service-scope`);
+* B) Put your service in the same scope as the dependency (or a narrower one). If
+ you depend on the ``client_configuration`` service, this means putting your
+ new service in the ``client`` scope (see :ref:`changing-service-scope`);
-* Pass the entire container to your service and retrieve your dependency from
+* C) Pass the entire container to your service and retrieve your dependency from
the container each time you need it to be sure you have the right instance
-- your service can live in the default ``container`` scope (see
:ref:`passing-container`);
@@ -102,49 +113,96 @@ Each scenario is detailed in the following sections.
.. _using-synchronized-service:
-Using a synchronized Service
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+A) Using a synchronized Service
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 2.3
Synchronized services are new in Symfony 2.3.
-Injecting the container or setting your service to a narrower scope have
-drawbacks. For synchronized services (like the ``request``), using setter
-injection is a nice option as it has no drawbacks and everything works
-without any special code in your service or in your definition::
+Both injecting the container and setting your service to a narrower scope have
+drawbacks. Assume first that the ``client_configuration`` service has been
+marked as ``synchronized``:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # app/config/config.yml
+ services:
+ client_configuration:
+ class: Acme\HelloBundle\Client\ClientConfiguration
+ scope: client
+ synchronized: true
+ # ...
+
+ .. code-block:: xml
+
+
+
+
+
+
+
+
+
+
+ .. code-block:: php
+
+ // app/config/config.php
+ use Symfony\Component\DependencyInjection\Definition;
+
+ $definition = new Definition(
+ 'Acme\HelloBundle\Client\ClientConfiguration',
+ array()
+ );
+ $definition->setScope('client');
+ $definition->setSynchronized(true);
+ $container->setDefinition('client_configuration', $definition);
+
+Now, if you inject this service using setter injection, there are no drawbacks
+and everything works without any special code in your service or in your definition::
// src/Acme/HelloBundle/Mail/Mailer.php
namespace Acme\HelloBundle\Mail;
- use Symfony\Component\HttpFoundation\Request;
+ use Acme\HelloBundle\Client\ClientConfiguration;
class Mailer
{
- protected $request;
+ protected $clientConfiguration;
- public function setRequest(Request $request = null)
+ public function setClientConfiguration(ClientConfiguration $clientConfiguration = null)
{
- $this->request = $request;
+ $this->clientConfiguration = $clientConfiguration;
}
public function sendEmail()
{
- if (null === $this->request) {
+ if (null === $this->clientConfiguration) {
// throw an error?
}
- // ... do something using the request here
+ // ... do something using the client configuration here
}
}
-Whenever the ``request`` scope is entered or left, the service container will
-automatically call the ``setRequest()`` method with the current ``request``
-instance.
+Whenever the ``client`` scope is entered or left, the service container will
+automatically call the ``setClientConfiguration()`` method with the current
+``client_configuration`` instance.
-You might have noticed that the ``setRequest()`` method accepts ``null`` as a
-valid value for the ``request`` argument. That's because when leaving the
-``request`` scope, the ``request`` instance can be ``null`` (for the master
-request for instance). Of course, you should take care of this possibility in
+You might have noticed that the ``setClientConfiguration()`` method accepts
+``null`` as a valid value for the ``client_configuration`` argument. That's
+because when leaving the ``client`` scope, the ``client_configuration`` instance
+can be ``null``. Of course, you should take care of this possibility in
your code. This should also be taken into account when declaring your service:
.. configuration-block::
@@ -153,20 +211,25 @@ your code. This should also be taken into account when declaring your service:
# src/Acme/HelloBundle/Resources/config/services.yml
services:
- greeting_card_manager:
- class: Acme\HelloBundle\Mail\GreetingCardManager
+ my_mailer:
+ class: Acme\HelloBundle\Mail\Mailer
calls:
- - [setRequest, ['@?request=']]
+ - [setClientConfiguration, ['@?client_configuration=']]
.. code-block:: xml
-
-
-
+
+
@@ -178,55 +241,25 @@ your code. This should also be taken into account when declaring your service:
use Symfony\Component\DependencyInjection\ContainerInterface;
$definition = $container->setDefinition(
- 'greeting_card_manager',
- new Definition('Acme\HelloBundle\Mail\GreetingCardManager')
+ 'my_mailer',
+ new Definition('Acme\HelloBundle\Mail\Mailer')
)
- ->addMethodCall('setRequest', array(
- new Reference('request', ContainerInterface::NULL_ON_INVALID_REFERENCE, false)
+ ->addMethodCall('setClientConfiguration', array(
+ new Reference(
+ 'client_configuration',
+ ContainerInterface::NULL_ON_INVALID_REFERENCE,
+ false
+ )
));
-.. tip::
-
- You can declare your own ``synchronized`` services very easily; here is
- the declaration of the ``request`` service for reference:
-
- .. configuration-block::
-
- .. code-block:: yaml
-
- services:
- request:
- scope: request
- synthetic: true
- synchronized: true
-
- .. code-block:: xml
-
-
-
-
-
- .. code-block:: php
-
- use Symfony\Component\DependencyInjection\Definition;
- use Symfony\Component\DependencyInjection\ContainerInterface;
-
- $definition = $container->setDefinition('request')
- ->setScope('request')
- ->setSynthetic(true)
- ->setSynchronized(true);
-
-.. caution::
-
- The service using the synchronized service will need to be public in order
- to have its setter called when the scope changes.
-
.. _changing-service-scope:
-Changing the Scope of your Service
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+B) Changing the Scope of your Service
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Changing the scope of a service should be done in its definition:
+Changing the scope of a service should be done in its definition. This example
+assumes that the ``Mailer`` class has a ``__construct`` function whose first
+argument is the ``ClientConfiguration`` object:
.. configuration-block::
@@ -234,20 +267,20 @@ Changing the scope of a service should be done in its definition:
# src/Acme/HelloBundle/Resources/config/services.yml
services:
- greeting_card_manager:
- class: Acme\HelloBundle\Mail\GreetingCardManager
- scope: request
- arguments: [@request]
+ my_mailer:
+ class: Acme\HelloBundle\Mail\Mailer
+ scope: client
+ arguments: [@client_configuration]
.. code-block:: xml
-
-
+
.. code-block:: php
@@ -256,17 +289,17 @@ Changing the scope of a service should be done in its definition:
use Symfony\Component\DependencyInjection\Definition;
$definition = $container->setDefinition(
- 'greeting_card_manager',
+ 'my_mailer',
new Definition(
- 'Acme\HelloBundle\Mail\GreetingCardManager',
- array(new Reference('request'),
+ 'Acme\HelloBundle\Mail\Mailer',
+ array(new Reference('client_configuration'),
))
- )->setScope('request');
+ )->setScope('client');
.. _passing-container:
-Passing the Container as a Dependency of your Service
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+C) Passing the Container as a Dependency of your Service
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Setting the scope to a narrower one is not always possible (for instance, a
twig extension must be in the ``container`` scope as the Twig environment
@@ -289,15 +322,15 @@ into your service::
public function sendEmail()
{
- $request = $this->container->get('request');
- // ... do something using the request here
+ $request = $this->container->get('client_configuration');
+ // ... do something using the client configuration here
}
}
.. caution::
- Take care not to store the request in a property of the object for a
- future call of the service as it would cause the same issue described
+ Take care not to store the client configuration in a property of the object
+ for a future call of the service as it would cause the same issue described
in the first section (except that Symfony cannot detect that you are
wrong).