Cover of the book Symfony 5: The Fast Track

Symfony 5: The Fast Track is the best book to learn modern Symfony development, from zero to production. +300 pages in full color showing how to combine Symfony with Docker, APIs, queues & async tasks, Webpack, Single-Page Applications, etc.

Buy printed version
WARNING: You are browsing the documentation for Symfony 2.2 which is not maintained anymore. Consider upgrading your projects to Symfony 5.1.

How to work with Scopes

2.2 version
Unmaintained

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

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 a Symfony\Component\DependencyInjection\Exception\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.

Setting the Scope in the Definition

The scope of a service is set in the definition of the service:

  • YAML
    1
    2
    3
    4
    5
    # src/Acme/HelloBundle/Resources/config/services.yml
    services:
        greeting_card_manager:
            class: Acme\HelloBundle\Mail\GreetingCardManager
            scope: request
    
  • XML
    1
    2
    3
    4
    <!-- src/Acme/HelloBundle/Resources/config/services.xml -->
    <services>
        <service id="greeting_card_manager" class="Acme\HelloBundle\Mail\GreetingCardManager" scope="request" />
    </services>
    
  • PHP
    1
    2
    3
    4
    5
    6
    7
    // src/Acme/HelloBundle/Resources/config/services.php
    use Symfony\Component\DependencyInjection\Definition;
    
    $container->setDefinition(
        'greeting_card_manager',
        new Definition('Acme\HelloBundle\Mail\GreetingCardManager')
    )->setScope('request');
    

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.

Using a Service from a narrower Scope

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.

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:

// 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
    10
    # 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). In some rare cases, it’s necessary when you have a service in the container scope that needs a service in the request scope.

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.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.