Table of Contents
Questions & Feedback
Found a typo or an error?
Want to improve this document? Edit it.
Need support or have a technical question?
Post to the user mailing-list.
Master Symfony2 fundamentals
Symfony hosting done right
Discover the SensioLabs Support
How to work with Scopes
How to work with Scopes¶
This entry is all about scopes, a somewhat advanced topic related to the
Service Container. If you've ever gotten an error mentioning
"scopes" when creating services, or need to create a service that depends
on the request service, then this entry is for you.
Understanding Scopes¶
The scope of a service controls how long an instance of a service is used by the container. The Dependency Injection component provides two generic scopes:
container(the default one): The same instance is used each time you request it from this container.prototype: A new instance is created each time you request the service.
The
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 service, you will receive
a ScopeWideningInjectionException
when compiling the container. Read the sidebar below for more details.
Note
A service can of course depend on a service from a wider scope without any issue.
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
requestinstance as it is synchronized with therequestscope (see Using a synchronized Service). - Put your service in the same scope as the dependency (or a narrower one). If
you depend on the
requestservice, this means putting your new service in therequestscope (see Changing the Scope of your Service); - 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
containerscope (see Passing the Container as a Dependency of your Service);
Each scenario is detailed in the following sections.
Using a synchronized Service¶
New in version 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 the best option as it has no drawbacks and everything works
without any special code in your service or in your definition:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // 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 scope is entered or left, 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:
- YAML
1 2 3 4 5 6
# src/Acme/HelloBundle/Resources/config/services.yml services: greeting_card_manager: class: Acme\HelloBundle\Mail\GreetingCardManager calls: - [setRequest, ['@?request']]
- XML
1 2 3 4 5 6 7 8 9
<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <services> <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>
- PHP
1 2 3 4 5 6 7 8 9 10 11
// 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 synchronized services very easily; here is
the declaration of the request service for reference:
- YAML
1 2 3 4 5
services: request: scope: request synthetic: true synchronized: true
- XML
1 2 3
<services> <service id="request" scope="request" synthetic="true" synchronized="true" /> </services>
- PHP
1 2 3 4 5 6 7
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\ContainerInterface; $definition = $container->setDefinition('request') ->setScope('request') ->setSynthetic(true) ->setSynchronized(true);
Changing the Scope of your Service¶
Changing the scope of a service should be done in its definition:
- YAML
# src/Acme/HelloBundle/Resources/config/services.yml services: greeting_card_manager: class: Acme\HelloBundle\Mail\GreetingCardManager scope: request arguments: [@request] - XML
1 2 3 4 5 6 7 8
<!-- 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>
- PHP
1 2 3 4 5 6 7 8 9 10
// 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 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | // src/Acme/HelloBundle/Mail/Mailer.php
namespace Acme\HelloBundle\Mail;
use Symfony\Component\DependencyInjection\ContainerInterface;
class Mailer
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function sendEmail()
{
$request = $this->container->get('request');
// ... do something using the request 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 in the first section (except that Symfony cannot detect that you are wrong).
The service config for this class would look something like this:
- YAML
1 2 3 4 5 6 7 8 9
# src/Acme/HelloBundle/Resources/config/services.yml parameters: # ... my_mailer.class: Acme\HelloBundle\Mail\Mailer services: my_mailer: class: "%my_mailer.class%" arguments: ["@service_container"] # scope: container can be omitted as it is the default
- XML
1 2 3 4 5 6 7 8 9 10 11
<!-- src/Acme/HelloBundle/Resources/config/services.xml --> <parameters> <!-- ... --> <parameter key="my_mailer.class">Acme\HelloBundle\Mail\Mailer</parameter> </parameters> <services> <service id="my_mailer" class="%my_mailer.class%"> <argument type="service" id="service_container" /> </service> </services>
- PHP
1 2 3 4 5 6 7 8 9 10 11
// src/Acme/HelloBundle/Resources/config/services.php use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Reference; // ... $container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mail\Mailer'); $container->setDefinition('my_mailer', new Definition( '%my_mailer.class%', array(new Reference('service_container')) ));
Note
Injecting the whole container into a service is generally not a good 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
The Request as a Controller Argument for details.





is a trademark of Fabien Potencier. All rights reserved.