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, then this entry is for you.

Note

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 (see Injecting the Request). 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.

The scope of a service controls how long an instance of a service is used
by the container. The DependencyInjection 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).

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 client_configuration service,
you will receive a
ScopeWideningInjectionException
when compiling the container. Read the sidebar below for more details.

Scopes and Dependencies

Imagine you've configured a my_mailer service. You haven't configured
the scope of the service, so it defaults to container. In other words,
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 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. There are several reasons why this presents a problem:

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. Call this
ConfigurationB.

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

Note

A service can of course depend on a service from a wider scope without
any issue.

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 B) Changing the Scope of your Service);

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

// src/AppBundle/Mail/Mailer.phpnamespaceAppBundle\Mail;useAppBundle\Client\ClientConfiguration;classMailer{protected$clientConfiguration;publicfunctionsetClientConfiguration(ClientConfiguration$clientConfiguration=null){$this->clientConfiguration=$clientConfiguration;}publicfunctionsendEmail(){if(null===$this->clientConfiguration){// throw an error?}// ... do something using the client configuration here}}

Whenever the client scope is active, the service container will
automatically call the setClientConfiguration() method when the
client_configuration service is set in the container.

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:

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/AppBundle/Mail/Mailer.phpnamespaceAppBundle\Mail;useSymfony\Component\DependencyInjection\ContainerInterface;classMailer{protected$container;publicfunction__construct(ContainerInterface$container){$this->container=$container;}publicfunctionsendEmail(){$request=$this->container->get('client_configuration');// ... do something using the client configuration here}}

Caution

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).

The service config for this class would look something like this:

YAML

1
2
3
4
5
6

# app/config/services.ymlservices:my_mailer:class:AppBundle\Mail\Mailerarguments:["@service_container"]# scope: container can be omitted as it is the default