概述 快速入门 教程 手册 最佳实践 组件 参考 贡献

发布于 2015-08-27 16:53:32 | 180 次阅读 | 评论: 0 | 来源: 网络整理

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.

注解

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

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

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

注解

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

Using a Service from a Narrower Scope

There are several solutions to the scope problem:

Each scenario is detailed in the following sections.

A) Using a Synchronized Service

2.3 新版功能: Synchronized services were introduced in Symfony 2.3.

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:

  • YAML
    # app/config/config.yml
    services:
        client_configuration:
            class:        AppBundleClientClientConfiguration
            scope:        client
            synchronized: true
            synthetic:    true
            # ...
    
  • 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"
        >
    
        <services>
            <service
                id="client_configuration"
                scope="client"
                synchronized="true"
                synthetic="true"
                class="AppBundleClientClientConfiguration"
            />
        </services>
    </container>
    
  • PHP
    // app/config/config.php
    use SymfonyComponentDependencyInjectionDefinition;
    
    $definition = new Definition(
        'AppBundleClientClientConfiguration',
        array()
    );
    $definition->setScope('client');
    $definition->setSynchronized(true);
    $definition->setSynthetic(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:

// src/AppBundle/Mail/Mailer.php
namespace AppBundleMail;

use AppBundleClientClientConfiguration;

class Mailer
{
    protected $clientConfiguration;

    public function setClientConfiguration(ClientConfiguration $clientConfiguration = null)
    {
        $this->clientConfiguration = $clientConfiguration;
    }

    public function sendEmail()
    {
        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:

  • YAML
    # app/config/services.yml
    services:
        my_mailer:
            class: AppBundleMailMailer
            calls:
                - [setClientConfiguration, ["@?client_configuration="]]
    
  • XML
    <!-- app/config/services.xml -->
    <services>
        <service id="my_mailer"
            class="AppBundleMailMailer"
        >
            <call method="setClientConfiguration">
                <argument
                    type="service"
                    id="client_configuration"
                    on-invalid="null"
                    strict="false"
                />
            </call>
        </service>
    </services>
    
  • PHP
    // app/config/services.php
    use SymfonyComponentDependencyInjectionDefinition;
    use SymfonyComponentDependencyInjectionContainerInterface;
    
    $definition = $container->setDefinition(
        'my_mailer',
        new Definition('AppBundleMailMailer')
    )
    ->addMethodCall('setClientConfiguration', array(
        new Reference(
            'client_configuration',
            ContainerInterface::NULL_ON_INVALID_REFERENCE,
            false
        )
    ));
    

B) Changing the Scope of your Service

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:

  • YAML
    # app/config/services.yml
    services:
        my_mailer:
            class: AppBundleMailMailer
            scope: client
            arguments: ["@client_configuration"]
    
  • XML
    <!-- app/config/services.xml -->
    <services>
        <service id="my_mailer"
                class="AppBundleMailMailer"
                scope="client">
                <argument type="service" id="client_configuration" />
        </service>
    </services>
    
  • PHP
    // app/config/services.php
    use SymfonyComponentDependencyInjectionDefinition;
    
    $definition = $container->setDefinition(
        'my_mailer',
        new Definition(
            'AppBundleMailMailer',
            array(new Reference('client_configuration'),
        ))
    )->setScope('client');
    

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 needs it as a dependency). In these cases, you can pass the entire container into your service:

// src/AppBundle/Mail/Mailer.php
namespace AppBundleMail;

use SymfonyComponentDependencyInjectionContainerInterface;

class Mailer
{
    protected $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function sendEmail()
    {
        $request = $this->container->get('client_configuration');
        // ... do something using the client configuration here
    }
}

警告

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
    # app/config/services.yml
    services:
        my_mailer:
            class:     AppBundleMailMailer
            arguments: ["@service_container"]
            # scope: container can be omitted as it is the default
    
  • XML
    <!-- app/config/services.xml -->
    <services>
        <service id="my_mailer" class="AppBundleMailMailer">
             <argument type="service" id="service_container" />
        </service>
    </services>
    
  • PHP
    // app/config/services.php
    use SymfonyComponentDependencyInjectionDefinition;
    use SymfonyComponentDependencyInjectionReference;
    
    $container->setDefinition('my_mailer', new Definition(
        'AppBundleMailMailer',
        array(new Reference('service_container'))
    ));
    

注解

Injecting the whole container into a service is generally not a good idea (only inject what you need).

最新网友评论  共有(0)条评论 发布评论 返回顶部

Copyright © 2007-2017 PHPERZ.COM All Rights Reserved   冀ICP备14009818号  版权声明  广告服务