Skip to content

[#2956] Refactoring the scopes entry #2972

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 12, 2013
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[#2972] Many small tweaks - thanks very much to @wouterj and @xabbuh
  • Loading branch information
weaverryan committed Nov 12, 2013
commit d417f7b4363011dacd6f91a99e94e602fe35f97d
2 changes: 2 additions & 0 deletions book/service_container.rst
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,8 @@ 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
~~~~~~~~~~~~~~~~~~~~~

Expand Down
102 changes: 61 additions & 41 deletions cookbook/service_container/scopes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +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. 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``.
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
--------------------
Expand All @@ -34,8 +35,8 @@ 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 Example: client Scope
~~~~~~~~~~~~~~~~~~~~~~~~~
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't it be better to write "no service in the default Symfony2 container belongs to [...]"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that's tough. I think it's actually "no services" because you would say "zero services" and "one service" (no == zero).

Expand Down Expand Up @@ -64,14 +65,14 @@ when compiling the container. Read the sidebar below for more 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 ``client_configuration`` service (let's
call it *ConfigurationA*) 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!

* 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. Let's call this
``client_configuration`` service into the container. Call this
*ConfigurationB*.

* Somewhere in your application, you once again ask for the ``my_mailer``
Expand All @@ -96,14 +97,14 @@ Using a Service from a narrower Scope

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
* 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`);
Expand All @@ -112,15 +113,15 @@ 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
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":
marked as ``synchronized``:

.. configuration-block::

Expand All @@ -132,17 +133,25 @@ marked as "synchronized":
class: Acme\HelloBundle\Client\ClientConfiguration
scope: client
synchronized: true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add # ...

# ...

.. code-block:: xml

<!-- app/config/config.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd"
>

<services>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<!-- ... -->

<service id="client_configuration" scope="client" synchronized="true" class="Acme\HelloBundle\Client\ClientConfiguration" />
<service
id="client_configuration"
scope="client"
synchronized="true"
class="Acme\HelloBundle\Client\ClientConfiguration"
/>
</services>
</container>

Expand All @@ -151,13 +160,13 @@ marked as "synchronized":
// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add // ... here


$defn = new Definition(
$definition = new Definition(
'Acme\HelloBundle\Client\ClientConfiguration',
array()
);
$defn->setScope('client');
$defn->setSynchronized(true);
$container->setDefinition('client_configuration', $defn);
$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::
Expand Down Expand Up @@ -202,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:
- [setClientConfiguration, ['@?client_configuration=']]

.. code-block:: xml

<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
<services>
<service id="greeting_card_manager"
class="Acme\HelloBundle\Mail\GreetingCardManager"
<service id="my_mailer"
class="Acme\HelloBundle\Mail\Mailer"
>
<call method="setClientConfiguration">
<argument type="service" id="client_configuration" on-invalid="null" strict="false" />
<argument
type="service"
id="client_configuration"
on-invalid="null"
strict="false"
/>
</call>
</service>
</services>
Expand All @@ -227,37 +241,43 @@ 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('setClientConfiguration', array(
new Reference('client_configuration', ContainerInterface::NULL_ON_INVALID_REFERENCE, false)
new Reference(
'client_configuration',
ContainerInterface::NULL_ON_INVALID_REFERENCE,
false
)
));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

->addMethodCall('setClientConfiguration', array(
    new Reference(
        'client_configuration',
        ContainerInterface::NULL_ON_INVALID_REFERENCE,
        false
    ),
));


.. _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::

.. code-block:: yaml

# src/Acme/HelloBundle/Resources/config/services.yml
services:
greeting_card_manager:
class: Acme\HelloBundle\Mail\GreetingCardManager
my_mailer:
class: Acme\HelloBundle\Mail\Mailer
scope: client
arguments: [@client_configuration]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we replace the GreetingCardManager with the Mailer a note should be added that the class need a constructor that accepts the client_configuration service as an argument. This is done similarly when showing how to pass the entire service container.


.. code-block:: xml

<!-- src/Acme/HelloBundle/Resources/config/services.xml -->
<services>
<service id="greeting_card_manager"
class="Acme\HelloBundle\Mail\GreetingCardManager"
<service id="my_mailer"
class="Acme\HelloBundle\Mail\Mailer"
scope="client"
/>
<argument type="service" id="client_configuration" />
Expand All @@ -269,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',
'Acme\HelloBundle\Mail\Mailer',
array(new Reference('client_configuration'),
))
)->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
Expand Down