Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

updated documentation for synchronized services #2343

Merged
merged 1 commit into from

5 participants

@fabpot
Owner
Q A
Doc fix? no
New docs? yes (symfony/symfony#7007 and symfony/symfony#7457)
Applies to 2.3
Fixed tickets n/a
@fabpot
Owner

I've updated the documentation by keeping the three possible ways to deal with scoped services, even if I think that the new way (using setter injection) should probably be always used. I've emphasized that in the text, but I don't know if we can do better.

cookbook/service_container/scopes.rst
@@ -69,10 +71,36 @@ when compiling the container. Read the sidebar below for more details.
A service can of course depend on a service from a wider scope without
any issue.
-Setting the Scope in the Definition
------------------------------------
+------
+If you don't specify the scope, it defaults to ``container``, which is what
+you want most of the time. Unless your service depends on another service
+that's scoped to a narrower scope, you probably don't need to set the scope.
+-------
@WouterJ Collaborator
WouterJ added a note

I have never seen this syntax? Is it a typo or is it something I don't know from sphinx?

@fabpot Owner
fabpot added a note

oops, that's a leftover that I've removed now. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cookbook/service_container/scopes.rst
((19 lines not shown))
-The scope of a service is set in the definition of the service:
+* Use setter injection if the dependency is "synchronized" (this is the best
+ solution for the ``request`` instance as it is synchronized with the
+ ``request`` scope).
@WouterJ Collaborator
WouterJ added a note

I think it will be a great if you put a reference link to each heading below in this list

@fabpot Owner
fabpot added a note

Good idea. Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cookbook/service_container/scopes.rst
@@ -83,12 +105,14 @@ The scope of a service is set in the definition of the service:
greeting_card_manager:
class: Acme\HelloBundle\Mail\GreetingCardManager
scope: request
+ arguments: [@request]
.. code-block:: xml
<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
<services>
<service id="greeting_card_manager" class="Acme\HelloBundle\Mail\GreetingCardManager" scope="request" />
@WouterJ Collaborator
WouterJ added a note

While it is not your fault, I prefer to do it like this: (to avoid horizontal scrollbars)

<service id="greeting_card_manager"
    class="Acme\HelloBundle\Mail\GreetingCardManager"
    scope="request"
/>
@fabpot Owner
fabpot added a note

fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cookbook/service_container/scopes.rst
((8 lines not shown))
+.. tip::
If you define a controller as a service then you can get the ``Request`` object
without injecting the container by having it passed in as an argument of your
action method. See :ref:`book-controller-request-argument` for details.
@WouterJ Collaborator
WouterJ added a note

If this is a tip, it should get indented

@fabpot Owner
fabpot added a note

fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cookbook/service_container/scopes.rst
((72 lines not shown))
+ <call method="setRequest">
+ <argument type="service" id="request" on-invalid="null" strict="false" />
+ </call>
+ </services>
+
+ .. code-block:: php
+
+ // src/Acme/HelloBundle/Resources/config/services.php
+ use Symfony\Component\DependencyInjection\Definition;
+ use Symfony\Component\DependencyInjection\ContainerInterface;
+
+ $definition = $container->setDefinition(
+ 'greeting_card_manager',
+ new Definition('Acme\HelloBundle\Mail\GreetingCardManager')
+ )
+ ->addMethodCall('setRequest', array(new Reference('request', ContainerInterface::NULL_ON_INVALID_REFERENCE, false)));
@WouterJ Collaborator
WouterJ added a note

I prefer some line breaking in this line too

@fabpot Owner
fabpot added a note

fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cookbook/service_container/scopes.rst
((76 lines not shown))
+
+ .. code-block:: php
+
+ // src/Acme/HelloBundle/Resources/config/services.php
+ use Symfony\Component\DependencyInjection\Definition;
+ use Symfony\Component\DependencyInjection\ContainerInterface;
+
+ $definition = $container->setDefinition(
+ 'greeting_card_manager',
+ new Definition('Acme\HelloBundle\Mail\GreetingCardManager')
+ )
+ ->addMethodCall('setRequest', array(new Reference('request', ContainerInterface::NULL_ON_INVALID_REFERENCE, false)));
+
+.. tip::
+
+ You can declare your own ```synchonized`` services very easily; here is
@WouterJ Collaborator
WouterJ added a note

typo: an extra backtick

@fabpot Owner
fabpot added a note

fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@WouterJ
Collaborator

@fabpot I think it is a good practise to put the most recommend practise as first. People don't like reading and they will almost always use the first option.

@fabpot
Owner

@WouterJ Not sure about that. That's also why I've first listed the 3 possibilities with short sentences and moved the discussions into their own sections. That way, people are aware of all possibilities and their pros and cons. If I put the best option first, then, why not removing the other two possibilities altogether as nobody cares anymore?

@fabpot fabpot referenced this pull request from a commit in symfony/symfony
@fabpot fabpot merged branch fabpot/request-scope (PR #7457)
This PR was merged into the master branch.

Discussion
----------

moved the request scope creation to the ContainerAwareHttpKernel class

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | symfony/symfony-docs#2343

While updating the scope documentation, I realized that the request scope was created in the FrameworkBundle while the HttpKernel that manages it was in the HttpKernel component. So, this PR makes things more consistent.

Commits
-------

cec98c1 [DependencyInjection] fixed PHP notice when the scope is not defined
550df5a moved the request scope creation to the ContainerAwareHttpKernel class
ddd30d0
@fabpot fabpot referenced this pull request from a commit in symfony/symfony
@fabpot fabpot merged branch fabpot/contagious-services (PR #7007)
This PR was merged into the master branch.

Discussion
----------

[2.3] [WIP] Synchronized services...

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #5300, #6756
| License       | MIT
| Doc PR        | symfony/symfony-docs#2343

Todo:

 - [x] update documentation
 - [x] find a better name than contagious (synchronized)?

refs #6932, refs #5012

This PR is a proof of concept that tries to find a solution for some problems we have with scopes and services depending on scoped services (mostly the request service in Symfony).

Basically, whenever you want to inject the Request into a service, you have two possibilities:

 * put your own service into the request scope (a new service will be created whenever a sub-request is run, and the service is not available outside the request scope);

 * set the request service reference as non-strict (your service is always available but the request you have depends on when the service is created the first time).

This PR addresses this issue by allowing to use the second option but you service still always has the right Request service (see below for a longer explanation on how it works).

There is another issue that this PR fixes: edge cases and weird behaviors. There are several bug reports about some weird behaviors, and most of the time, this is related to the sub-requests. That's because the Request is injected into several Symfony objects without being updated correctly when leaving the request scope. Let me explain that: when a listener for instance needs the Request object, it can listen to the `kernel.request` event and store the request somewhere. So, whenever you enter a sub-request, the listener will get the new one. But when the sub-request ends, the listener has no way to know that it needs to reset the request to the master one. In practice, that's not really an issue, but let me show you an example of this issue in practice:

 * You have a controller that is called with the English locale;
 * The controller (probably via a template) renders a sub-request that uses the French locale;
 *  After the rendering, and from the controller, you try to generate a URL. Which locale the router will use? Yes, the French locale, which is wrong.

To fix these issues, this PR introduces a new notion in the DIC: synchronized services. When a service is marked as synchronized, all method calls involving this service will be called each time this service is set. When in a scope, methods are also called to restore the previous version of the service when the scope leaves.

If you have a look at the router or the locale listener, you will see that there is now a `setRequest` method that will called whenever the request service changes (because the `Container::set()` method is called or because the service is changed by a scope change).

Commits
-------

17269e1 [DependencyInjection] fixed management of scoped services with an invalid behavior set to null
bb83b3e [HttpKernel] added a safeguard for when a fragment is rendered outside the context of a master request
5d7b835 [FrameworkBundle] added some functional tests
ff9d688 fixed Request management for FragmentHandler
1b98ad3 fixed Request management for LocaleListener
a7b2b7e fixed Request management for RequestListener
0892135 [HttpKernel] ensured that the Request is null when outside of the Request scope
2ffcfb9 [FrameworkBundle] made the Request service synchronized
ec1e7ca [DependencyInjection] added a way to automatically update scoped services
74f96bf
@fabpot fabpot referenced this pull request from a commit in symfony/FrameworkBundle
@fabpot fabpot merged branch fabpot/request-scope (PR #7457)
This PR was merged into the master branch.

Discussion
----------

moved the request scope creation to the ContainerAwareHttpKernel class

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | symfony/symfony-docs#2343

While updating the scope documentation, I realized that the request scope was created in the FrameworkBundle while the HttpKernel that manages it was in the HttpKernel component. So, this PR makes things more consistent.

Commits
-------

cec98c1 [DependencyInjection] fixed PHP notice when the scope is not defined
550df5a moved the request scope creation to the ContainerAwareHttpKernel class
c8d9443
@fabpot fabpot referenced this pull request from a commit in symfony/FrameworkBundle
@fabpot fabpot merged branch fabpot/contagious-services (PR #7007)
This PR was merged into the master branch.

Discussion
----------

[2.3] [WIP] Synchronized services...

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #5300, #6756
| License       | MIT
| Doc PR        | symfony/symfony-docs#2343

Todo:

 - [x] update documentation
 - [x] find a better name than contagious (synchronized)?

refs #6932, refs #5012

This PR is a proof of concept that tries to find a solution for some problems we have with scopes and services depending on scoped services (mostly the request service in Symfony).

Basically, whenever you want to inject the Request into a service, you have two possibilities:

 * put your own service into the request scope (a new service will be created whenever a sub-request is run, and the service is not available outside the request scope);

 * set the request service reference as non-strict (your service is always available but the request you have depends on when the service is created the first time).

This PR addresses this issue by allowing to use the second option but you service still always has the right Request service (see below for a longer explanation on how it works).

There is another issue that this PR fixes: edge cases and weird behaviors. There are several bug reports about some weird behaviors, and most of the time, this is related to the sub-requests. That's because the Request is injected into several Symfony objects without being updated correctly when leaving the request scope. Let me explain that: when a listener for instance needs the Request object, it can listen to the `kernel.request` event and store the request somewhere. So, whenever you enter a sub-request, the listener will get the new one. But when the sub-request ends, the listener has no way to know that it needs to reset the request to the master one. In practice, that's not really an issue, but let me show you an example of this issue in practice:

 * You have a controller that is called with the English locale;
 * The controller (probably via a template) renders a sub-request that uses the French locale;
 *  After the rendering, and from the controller, you try to generate a URL. Which locale the router will use? Yes, the French locale, which is wrong.

To fix these issues, this PR introduces a new notion in the DIC: synchronized services. When a service is marked as synchronized, all method calls involving this service will be called each time this service is set. When in a scope, methods are also called to restore the previous version of the service when the scope leaves.

If you have a look at the router or the locale listener, you will see that there is now a `setRequest` method that will called whenever the request service changes (because the `Container::set()` method is called or because the service is changed by a scope change).

Commits
-------

17269e1 [DependencyInjection] fixed management of scoped services with an invalid behavior set to null
bb83b3e [HttpKernel] added a safeguard for when a fragment is rendered outside the context of a master request
5d7b835 [FrameworkBundle] added some functional tests
ff9d688 fixed Request management for FragmentHandler
1b98ad3 fixed Request management for LocaleListener
a7b2b7e fixed Request management for RequestListener
0892135 [HttpKernel] ensured that the Request is null when outside of the Request scope
2ffcfb9 [FrameworkBundle] made the Request service synchronized
ec1e7ca [DependencyInjection] added a way to automatically update scoped services
4b56e49
@fabpot fabpot referenced this pull request from a commit in symfony/HttpKernel
@fabpot fabpot merged branch fabpot/request-scope (PR #7457)
This PR was merged into the master branch.

Discussion
----------

moved the request scope creation to the ContainerAwareHttpKernel class

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | symfony/symfony-docs#2343

While updating the scope documentation, I realized that the request scope was created in the FrameworkBundle while the HttpKernel that manages it was in the HttpKernel component. So, this PR makes things more consistent.

Commits
-------

cec98c1 [DependencyInjection] fixed PHP notice when the scope is not defined
550df5a moved the request scope creation to the ContainerAwareHttpKernel class
bdc1ffe
@fabpot fabpot referenced this pull request from a commit in symfony/HttpKernel
@fabpot fabpot merged branch fabpot/contagious-services (PR #7007)
This PR was merged into the master branch.

Discussion
----------

[2.3] [WIP] Synchronized services...

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #5300, #6756
| License       | MIT
| Doc PR        | symfony/symfony-docs#2343

Todo:

 - [x] update documentation
 - [x] find a better name than contagious (synchronized)?

refs #6932, refs #5012

This PR is a proof of concept that tries to find a solution for some problems we have with scopes and services depending on scoped services (mostly the request service in Symfony).

Basically, whenever you want to inject the Request into a service, you have two possibilities:

 * put your own service into the request scope (a new service will be created whenever a sub-request is run, and the service is not available outside the request scope);

 * set the request service reference as non-strict (your service is always available but the request you have depends on when the service is created the first time).

This PR addresses this issue by allowing to use the second option but you service still always has the right Request service (see below for a longer explanation on how it works).

There is another issue that this PR fixes: edge cases and weird behaviors. There are several bug reports about some weird behaviors, and most of the time, this is related to the sub-requests. That's because the Request is injected into several Symfony objects without being updated correctly when leaving the request scope. Let me explain that: when a listener for instance needs the Request object, it can listen to the `kernel.request` event and store the request somewhere. So, whenever you enter a sub-request, the listener will get the new one. But when the sub-request ends, the listener has no way to know that it needs to reset the request to the master one. In practice, that's not really an issue, but let me show you an example of this issue in practice:

 * You have a controller that is called with the English locale;
 * The controller (probably via a template) renders a sub-request that uses the French locale;
 *  After the rendering, and from the controller, you try to generate a URL. Which locale the router will use? Yes, the French locale, which is wrong.

To fix these issues, this PR introduces a new notion in the DIC: synchronized services. When a service is marked as synchronized, all method calls involving this service will be called each time this service is set. When in a scope, methods are also called to restore the previous version of the service when the scope leaves.

If you have a look at the router or the locale listener, you will see that there is now a `setRequest` method that will called whenever the request service changes (because the `Container::set()` method is called or because the service is changed by a scope change).

Commits
-------

17269e1 [DependencyInjection] fixed management of scoped services with an invalid behavior set to null
bb83b3e [HttpKernel] added a safeguard for when a fragment is rendered outside the context of a master request
5d7b835 [FrameworkBundle] added some functional tests
ff9d688 fixed Request management for FragmentHandler
1b98ad3 fixed Request management for LocaleListener
a7b2b7e fixed Request management for RequestListener
0892135 [HttpKernel] ensured that the Request is null when outside of the Request scope
2ffcfb9 [FrameworkBundle] made the Request service synchronized
ec1e7ca [DependencyInjection] added a way to automatically update scoped services
82a3e6b
cookbook/service_container/scopes.rst
((54 lines not shown))
+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
+your code. This should also be taken into account when declaring your service:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # src/Acme/HelloBundle/Resources/config/services.yml
+ services:
+ greeting_card_manager:
+ class: Acme\HelloBundle\Mail\GreetingCardManager
+ calls:
+ - [setRequest, ['@?request']]
@stof
stof added a note

this is not equivalent to the XML format. There is no on-invalid="null" (which cannot be set in YAML btw contrary to on-invalid="ignore")

@fabpot Owner
fabpot added a note

But it works the exact same way in this case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
cookbook/service_container/scopes.rst
((84 lines not shown))
+
+ // src/Acme/HelloBundle/Resources/config/services.php
+ use Symfony\Component\DependencyInjection\Definition;
+ use Symfony\Component\DependencyInjection\ContainerInterface;
+
+ $definition = $container->setDefinition(
+ 'greeting_card_manager',
+ new Definition('Acme\HelloBundle\Mail\GreetingCardManager')
+ )
+ ->addMethodCall('setRequest', array(
+ new Reference('request', ContainerInterface::NULL_ON_INVALID_REFERENCE, false)
+ ));
+
+.. tip::
+
+ You can declare your own ``synchonized`` services very easily; here is the
@stof
stof added a note

typo. missing r in synchronized

@fabpot Owner
fabpot added a note

fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@richardmiller

@fabpot SInce the new synchronized services are a much better solution than the other two I am inclined to agree with @WouterJ that this should be given first and that the other two should be discussed as alternatives afterwards. With it explicitly saying they are worse solutions but that they are documented for historical reasons since they were the available solutions prior to the introduction of synchronized services.

Should there be a note block to say that this is new to 2.3 as well?

@WouterJ
Collaborator

I think we should also create a PR which includes the other options for the older branches.

@fabpot
Owner

I've updated the docs to put the recommended solution first. Should be ready to merge now.

@weaverryan weaverryan merged commit 2821a55 into from
@stof stof commented on the diff
cookbook/service_container/scopes.rst
@@ -82,42 +145,117 @@ The scope of a service is set in the definition of the service:
services:
greeting_card_manager:
class: Acme\HelloBundle\Mail\GreetingCardManager
- scope: request
+ calls:
+ - [setRequest, ['@?request']]
@stof
stof added a note

shouldn't this be '@?request=' to have both on-invalid="ignore" and strict="false" ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@fabpot fabpot referenced this pull request from a commit in symfony/DependencyInjection
@fabpot fabpot merged branch fabpot/contagious-services (PR #7007)
This PR was merged into the master branch.

Discussion
----------

[2.3] [WIP] Synchronized services...

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #5300, #6756
| License       | MIT
| Doc PR        | symfony/symfony-docs#2343

Todo:

 - [x] update documentation
 - [x] find a better name than contagious (synchronized)?

refs #6932, refs #5012

This PR is a proof of concept that tries to find a solution for some problems we have with scopes and services depending on scoped services (mostly the request service in Symfony).

Basically, whenever you want to inject the Request into a service, you have two possibilities:

 * put your own service into the request scope (a new service will be created whenever a sub-request is run, and the service is not available outside the request scope);

 * set the request service reference as non-strict (your service is always available but the request you have depends on when the service is created the first time).

This PR addresses this issue by allowing to use the second option but you service still always has the right Request service (see below for a longer explanation on how it works).

There is another issue that this PR fixes: edge cases and weird behaviors. There are several bug reports about some weird behaviors, and most of the time, this is related to the sub-requests. That's because the Request is injected into several Symfony objects without being updated correctly when leaving the request scope. Let me explain that: when a listener for instance needs the Request object, it can listen to the `kernel.request` event and store the request somewhere. So, whenever you enter a sub-request, the listener will get the new one. But when the sub-request ends, the listener has no way to know that it needs to reset the request to the master one. In practice, that's not really an issue, but let me show you an example of this issue in practice:

 * You have a controller that is called with the English locale;
 * The controller (probably via a template) renders a sub-request that uses the French locale;
 *  After the rendering, and from the controller, you try to generate a URL. Which locale the router will use? Yes, the French locale, which is wrong.

To fix these issues, this PR introduces a new notion in the DIC: synchronized services. When a service is marked as synchronized, all method calls involving this service will be called each time this service is set. When in a scope, methods are also called to restore the previous version of the service when the scope leaves.

If you have a look at the router or the locale listener, you will see that there is now a `setRequest` method that will called whenever the request service changes (because the `Container::set()` method is called or because the service is changed by a scope change).

Commits
-------

17269e1 [DependencyInjection] fixed management of scoped services with an invalid behavior set to null
bb83b3e [HttpKernel] added a safeguard for when a fragment is rendered outside the context of a master request
5d7b835 [FrameworkBundle] added some functional tests
ff9d688 fixed Request management for FragmentHandler
1b98ad3 fixed Request management for LocaleListener
a7b2b7e fixed Request management for RequestListener
0892135 [HttpKernel] ensured that the Request is null when outside of the Request scope
2ffcfb9 [FrameworkBundle] made the Request service synchronized
ec1e7ca [DependencyInjection] added a way to automatically update scoped services
bdbd94e
@fabpot fabpot referenced this pull request from a commit in symfony/TwigBridge
@fabpot fabpot merged branch fabpot/contagious-services (PR #7007)
This PR was merged into the master branch.

Discussion
----------

[2.3] [WIP] Synchronized services...

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #5300, #6756
| License       | MIT
| Doc PR        | symfony/symfony-docs#2343

Todo:

 - [x] update documentation
 - [x] find a better name than contagious (synchronized)?

refs #6932, refs #5012

This PR is a proof of concept that tries to find a solution for some problems we have with scopes and services depending on scoped services (mostly the request service in Symfony).

Basically, whenever you want to inject the Request into a service, you have two possibilities:

 * put your own service into the request scope (a new service will be created whenever a sub-request is run, and the service is not available outside the request scope);

 * set the request service reference as non-strict (your service is always available but the request you have depends on when the service is created the first time).

This PR addresses this issue by allowing to use the second option but you service still always has the right Request service (see below for a longer explanation on how it works).

There is another issue that this PR fixes: edge cases and weird behaviors. There are several bug reports about some weird behaviors, and most of the time, this is related to the sub-requests. That's because the Request is injected into several Symfony objects without being updated correctly when leaving the request scope. Let me explain that: when a listener for instance needs the Request object, it can listen to the `kernel.request` event and store the request somewhere. So, whenever you enter a sub-request, the listener will get the new one. But when the sub-request ends, the listener has no way to know that it needs to reset the request to the master one. In practice, that's not really an issue, but let me show you an example of this issue in practice:

 * You have a controller that is called with the English locale;
 * The controller (probably via a template) renders a sub-request that uses the French locale;
 *  After the rendering, and from the controller, you try to generate a URL. Which locale the router will use? Yes, the French locale, which is wrong.

To fix these issues, this PR introduces a new notion in the DIC: synchronized services. When a service is marked as synchronized, all method calls involving this service will be called each time this service is set. When in a scope, methods are also called to restore the previous version of the service when the scope leaves.

If you have a look at the router or the locale listener, you will see that there is now a `setRequest` method that will called whenever the request service changes (because the `Container::set()` method is called or because the service is changed by a scope change).

Commits
-------

17269e1 [DependencyInjection] fixed management of scoped services with an invalid behavior set to null
bb83b3e [HttpKernel] added a safeguard for when a fragment is rendered outside the context of a master request
5d7b835 [FrameworkBundle] added some functional tests
ff9d688 fixed Request management for FragmentHandler
1b98ad3 fixed Request management for LocaleListener
a7b2b7e fixed Request management for RequestListener
0892135 [HttpKernel] ensured that the Request is null when outside of the Request scope
2ffcfb9 [FrameworkBundle] made the Request service synchronized
ec1e7ca [DependencyInjection] added a way to automatically update scoped services
6cb9d55
@fabpot fabpot referenced this pull request from a commit in symfony/FrameworkBundle
@fabpot fabpot merged branch fabpot/request-scope (PR #7457)
This PR was merged into the master branch.

Discussion
----------

moved the request scope creation to the ContainerAwareHttpKernel class

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | symfony/symfony-docs#2343

While updating the scope documentation, I realized that the request scope was created in the FrameworkBundle while the HttpKernel that manages it was in the HttpKernel component. So, this PR makes things more consistent.

Commits
-------

cec98c1 [DependencyInjection] fixed PHP notice when the scope is not defined
550df5a moved the request scope creation to the ContainerAwareHttpKernel class
ea19044
@fabpot fabpot referenced this pull request from a commit in symfony/FrameworkBundle
@fabpot fabpot merged branch fabpot/contagious-services (PR #7007)
This PR was merged into the master branch.

Discussion
----------

[2.3] [WIP] Synchronized services...

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #5300, #6756
| License       | MIT
| Doc PR        | symfony/symfony-docs#2343

Todo:

 - [x] update documentation
 - [x] find a better name than contagious (synchronized)?

refs #6932, refs #5012

This PR is a proof of concept that tries to find a solution for some problems we have with scopes and services depending on scoped services (mostly the request service in Symfony).

Basically, whenever you want to inject the Request into a service, you have two possibilities:

 * put your own service into the request scope (a new service will be created whenever a sub-request is run, and the service is not available outside the request scope);

 * set the request service reference as non-strict (your service is always available but the request you have depends on when the service is created the first time).

This PR addresses this issue by allowing to use the second option but you service still always has the right Request service (see below for a longer explanation on how it works).

There is another issue that this PR fixes: edge cases and weird behaviors. There are several bug reports about some weird behaviors, and most of the time, this is related to the sub-requests. That's because the Request is injected into several Symfony objects without being updated correctly when leaving the request scope. Let me explain that: when a listener for instance needs the Request object, it can listen to the `kernel.request` event and store the request somewhere. So, whenever you enter a sub-request, the listener will get the new one. But when the sub-request ends, the listener has no way to know that it needs to reset the request to the master one. In practice, that's not really an issue, but let me show you an example of this issue in practice:

 * You have a controller that is called with the English locale;
 * The controller (probably via a template) renders a sub-request that uses the French locale;
 *  After the rendering, and from the controller, you try to generate a URL. Which locale the router will use? Yes, the French locale, which is wrong.

To fix these issues, this PR introduces a new notion in the DIC: synchronized services. When a service is marked as synchronized, all method calls involving this service will be called each time this service is set. When in a scope, methods are also called to restore the previous version of the service when the scope leaves.

If you have a look at the router or the locale listener, you will see that there is now a `setRequest` method that will called whenever the request service changes (because the `Container::set()` method is called or because the service is changed by a scope change).

Commits
-------

17269e1 [DependencyInjection] fixed management of scoped services with an invalid behavior set to null
bb83b3e [HttpKernel] added a safeguard for when a fragment is rendered outside the context of a master request
5d7b835 [FrameworkBundle] added some functional tests
ff9d688 fixed Request management for FragmentHandler
1b98ad3 fixed Request management for LocaleListener
a7b2b7e fixed Request management for RequestListener
0892135 [HttpKernel] ensured that the Request is null when outside of the Request scope
2ffcfb9 [FrameworkBundle] made the Request service synchronized
ec1e7ca [DependencyInjection] added a way to automatically update scoped services
7e17616
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 26, 2013
  1. @fabpot
This page is out of date. Refresh to see the latest.
Showing with 171 additions and 33 deletions.
  1. +171 −33 cookbook/service_container/scopes.rst
View
204 cookbook/service_container/scopes.rst
@@ -21,13 +21,15 @@ scopes:
- ``prototype``: A new instance is created each time you request the service.
-The FrameworkBundle 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).
+The
+:class:`Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel`
+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).
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`` component, you'll receive
+``my_foo`` service, but try to inject the ``request`` service, you will receive
a :class:`Symfony\\Component\\DependencyInjection\\Exception\\ScopeWideningInjectionException`
when compiling the container. Read the sidebar below for more details.
@@ -69,10 +71,71 @@ when compiling the container. Read the sidebar below for more details.
A service can of course depend on a service from a wider scope without
any issue.
-Setting the Scope in the Definition
------------------------------------
+Using a Service from a narrower Scope
+-------------------------------------
+
+If your service has a dependency on a scoped service (like the ``request``),
+you have three ways to deal with it:
+
+* Use setter injection if the dependency is "synchronized"; this is the
+ recommended way and the best solution for the ``request`` instance as it is
+ synchronized with the ``request`` scope (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`);
+
+* 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`);
+
+Each scenario is detailed in the following sections.
+
+.. _using-synchronized-service:
+
+Using a synchronized Service
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-The scope of a service is set in the definition of the service:
+Injecting the container or setting your service to a narrower scope have
+drawbacks. For synchronized services (like the ``request``), using setter
+injection is the best option as it has 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;
+
+ class Mailer
+ {
+ protected $request;
+
+ public function setRequest(Request $request = null)
+ {
+ $this->request = $request;
+ }
+
+ public function sendEmail()
+ {
+ if (null === $this->request) {
+ // throw an error?
+ }
+
+ // ... do something using the request here
+ }
+ }
+
+Whenever the ``request`` is entered or leaved, the service container will
+automatically call the ``setRequest()`` method with the current ``request``
+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
+your code. This should also be taken into account when declaring your service:
.. configuration-block::
@@ -82,42 +145,117 @@ The scope of a service is set in the definition of the service:
services:
greeting_card_manager:
class: Acme\HelloBundle\Mail\GreetingCardManager
- scope: request
+ calls:
+ - [setRequest, ['@?request']]
@stof
stof added a note

shouldn't this be '@?request=' to have both on-invalid="ignore" and strict="false" ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
.. code-block:: xml
<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
<services>
- <service id="greeting_card_manager" class="Acme\HelloBundle\Mail\GreetingCardManager" scope="request" />
+ <service id="greeting_card_manager"
+ class="Acme\HelloBundle\Mail\GreetingCardManager"
+ />
+ <call method="setRequest">
+ <argument type="service" id="request" on-invalid="null" strict="false" />
+ </call>
</services>
.. code-block:: php
// src/Acme/HelloBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;
+ use Symfony\Component\DependencyInjection\ContainerInterface;
- $container->setDefinition(
+ $definition = $container->setDefinition(
'greeting_card_manager',
new Definition('Acme\HelloBundle\Mail\GreetingCardManager')
- )->setScope('request');
+ )
+ ->addMethodCall('setRequest', array(
+ new Reference('request', ContainerInterface::NULL_ON_INVALID_REFERENCE, false)
+ ));
-If you don't specify the scope, it defaults to ``container``, which is what
-you want most of the time. Unless your service depends on another service
-that's scoped to a narrower scope (most commonly, the ``request`` service),
-you probably don't need to set the scope.
+.. tip::
-Using a Service from a narrower Scope
--------------------------------------
+ You can declare your own ``synchronized`` services very easily; here is
+ the declaration of the ``request`` service for reference:
+
+ .. configuration-block::
+
+ .. code-block:: yaml
-If your service depends on a scoped service, the best solution is to put
-it in the same scope (or a narrower one). Usually, this means putting your
-new service in the ``request`` scope.
+ services:
+ request:
+ scope: request
+ synthetic: true
+ synchronized: true
-But this is not always possible (for instance, a twig extension must be in
-the ``container`` scope as the Twig environment needs it as a dependency).
-In these cases, you should pass the entire container into your service and
-retrieve your dependency from the container each time you need it to be sure
-you have the right instance::
+ .. code-block:: xml
+
+ <services>
+ <service id="request" scope="request" synthetic="true" synchronized="true" />
+ </services>
+
+ .. code-block:: php
+
+ use Symfony\Component\DependencyInjection\Definition;
+ use Symfony\Component\DependencyInjection\ContainerInterface;
+
+ $definition = $container->setDefinition('request')
+ ->setScope('request')
+ ->setSynthetic(true)
+ ->setSynchronized(true);
+
+.. _changing-service-scope:
+
+Changing the Scope of your Service
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Changing the scope of a service should be done set in its definition:
+
+.. configuration-block::
+
+ .. code-block:: yaml
+
+ # src/Acme/HelloBundle/Resources/config/services.yml
+ services:
+ greeting_card_manager:
+ class: Acme\HelloBundle\Mail\GreetingCardManager
+ scope: request
+ arguments: [@request]
+
+ .. code-block:: xml
+
+ <!-- src/Acme/HelloBundle/Resources/config/services.xml -->
+ <services>
+ <service id="greeting_card_manager"
+ class="Acme\HelloBundle\Mail\GreetingCardManager"
+ scope="request"
+ />
+ <argument type="service" id="request" />
+ </services>
+
+ .. code-block:: php
+
+ // src/Acme/HelloBundle/Resources/config/services.php
+ use Symfony\Component\DependencyInjection\Definition;
+
+ $definition = $container->setDefinition(
+ 'greeting_card_manager',
+ new Definition(
+ 'Acme\HelloBundle\Mail\GreetingCardManager',
+ array(new Reference('request'),
+ ))
+ )->setScope('request');
+
+.. _passing-container:
+
+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
+needs it as a dependency). In these cases, you can pass the entire container
+into your service::
// src/Acme/HelloBundle/Mail/Mailer.php
namespace Acme\HelloBundle\Mail;
@@ -160,8 +298,7 @@ The service config for this class would look something like this:
services:
my_mailer:
class: "%my_mailer.class%"
- arguments:
- - "@service_container"
+ arguments: ["@service_container"]
# scope: container can be omitted as it is the default
.. code-block:: xml
@@ -195,10 +332,11 @@ The service config for this class would look something like this:
.. note::
Injecting the whole container into a service is generally not a good
- idea (only inject what you need). In some rare cases, it's necessary
- when you have a service in the ``container`` scope that needs a service
- in the ``request`` scope.
+ idea (only inject what you need).
+
+.. tip::
-If you define a controller as a service then you can get the ``Request`` object
-without injecting the container by having it passed in as an argument of your
-action method. See :ref:`book-controller-request-argument` for details.
+ If you define a controller as a service then you can get the ``Request``
+ object without injecting the container by having it passed in as an
+ argument of your action method. See
+ :ref:`book-controller-request-argument` for details.
Something went wrong with that request. Please try again.