如何创建服务的假名并标记服务为私有

3.4 版本
维护中的版本

将服务标记为public/private 

当服务被定义时,你通常希望能够在应用程序中去访问这些定义。这时的服务被称为public(公开)。例如,使用DoctrineBundle时,在容器中被注册的doctrine服务就是public服务。这表示你可以从容器中取出它,利用get()方法:

1
$doctrine = $container->get('doctrine');

在一些例子中,某个服务的存在,仅仅是为了被注入到另一个服务中,而并需要像上面那样从container中直接取出。

在这些例子中,为了得到些许性能提升,你应该设置服务为公开(private):

1
2
3
4
services:
   foo:
     class: Example\Foo
     public: false
1
2
3
4
5
6
7
8
9
<?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="foo" class="Example\Foo" public="false" />
    </services>
</container>
1
2
3
4
5
use Symfony\Component\DependencyInjection\Definition;
 
$definition = new Definition('Example\Foo');
$definition->setPublic(false);
$container->setDefinition('foo', $definition);

令私有服务变得特殊的是,如果它们只被注入一次,它们就会被从“服务”转化为“inlined instantiations行内实例”(如:new PrivateThing()),因为这可以提升容器性能。

现在,服务变为私有,你不应该直接从容器中出取它:

1
$container->get('foo');

上述代码能否工作,取决于这个服务是否可以成为行内实例(inlined instantiations)。简单说就是:一个服务可以被标记为私有,如果你不希望它从你的代码中被直接访问到。

然而,如果一个服务被标记为私有,你仍可利用假名(alias,见下例)来访问该服务(通过假名)。

服务默认都是公开的。

合成服务(Synthetic Services) 

译注:本小节乃是文档遗产,有的概念尚存在于最新版框架中,有的却已经被弱化或等待删除。虽然翻译日期久远,但从原理上讲,这部分内容不忍删除,在此列出仅供参考。

合成服务的最新文档以独立章节如何注入实例到容器中重新登场。

合成服务,是指那些注入到容器中,而不是被容器创建出来的服务。

例如,你正在使用HttpKernel组件,配合DependencyInjection组件,然后,当进入请求范围(request scope)之内时,request服务被注入到ContainerAwareHttpKernel::handle()方法中。当没有request请求时,这个类并不存在,所以该服务并不能包含在容器配置信息中。同样,该服务也与程序中的任何一个子请求(subrequest)不同。

为了创建合成服务,添加synthetic键并取值为true

1
2
3
services:
    request:
        synthetic: true
1
2
3
4
5
6
7
8
9
<?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="request" synthetic="true" />
    </services>
</container>
1
2
3
4
5
use Symfony\Component\DependencyInjection\Definition;
 
$container
    ->setDefinition('request', new Definition())
    ->setSynthetic(true);

以上显示,只有synthetic选项被设置,所有其他选项,都只能用于配置那些“被容器创建”的服务。因为request服务不是由容器创建,所以其他选项被忽略。

现在,你可以把你的类实例注入到容器以使其成为request服务,通过Container::set方法:

1
2
// ...
$container->set('request', new MyRequest(...));

假名/Aliasing 

有时你需要使用快捷方式来访问某些服务。你可以使用假名来实现,甚至,你可以对非公有(non-public)服务使用假名。

1
2
3
4
5
services:
   foo:
     class: Example\Foo
   bar:
     alias: foo
1
2
3
4
5
6
7
8
9
10
11
<?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="foo" class="Example\Foo" />
 
        <service id="bar" alias="foo" />
    </services>
</container>
1
2
3
4
5
use Symfony\Component\DependencyInjection\Definition;
 
$container->setDefinition('foo', new Definition('Example\Foo'));
 
$containerBuilder->setAlias('bar', 'foo');

这意味着,当直接使用容器时,可以通过请求bar服务来访问到foo服务:

1
$container->get('bar'); // Would return the foo service 返回的是foo服务

在YAML中,你可以直接使用对假名使用简便方式:

1
2
3
4
services:
   foo:
     class: Example\Foo
   bar: '@foo'

包容文件 

译注:本小节经过Symfony文档梳理,现已转移到其他章节中,参考这里

也许有这样的使用场景,你需要在某个服务自身被加载之前,包容另一个文件进来。此时你可以直接使用file键。

1
2
3
4
services:
   foo:
     class: Example\Foo\Bar
     file: "%kernel.root_dir%/src/path/to/file/foo.php"
1
2
3
4
5
6
7
8
9
10
11
<?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="foo" class="Example\Foo\Bar">
            <file>%kernel.root_dir%/src/path/to/file/foo.php</file>
        </service>
    </services>
</container>
1
2
3
4
5
use Symfony\Component\DependencyInjection\Definition;
 
$definition = new Definition('Example\Foo\Bar');
$definition->setFile('%kernel.root_dir%/src/path/to/file/foo.php');
$container->setDefinition('foo', $definition);

要注意Symfony在内部是调用PHP的requuire_once语法,这表明你的文件在页面每次请求中只被包容一次。

Deprecating Services(不建议使用的服务) 

一旦你决定不再推荐使用某个服务(比如它已过期,或者你决定不再对其进行任何维护),你可以在定义中deprecate它:

1
2
3
acme.my_service:
    class: ...
    deprecated: The "%service_id%" service is deprecated since 2.8 and will be removed in 3.0.
1
2
3
4
5
6
7
8
9
10
11
<?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="acme.my_service" class="...">
            <deprecated>The "%service_id%" service is deprecated since 2.8 and will be removed in 3.0.</deprecated>
        </service>
    </services>
</container>
1
2
3
4
5
6
7
$container
    ->register('acme.my_service', '...')
    ->setDeprecated(
        true,
        'The "%service_id%" service is deprecated since 2.8 and will be removed in 3.0.'
    )
;

现在,每当这个服务被使用时,都会触发一条“不建议使用”的警告,建议用户停止或调整对该服务的使用。

提示信息利用了模板,它可以替换%service_id%占位符为真实服务的id。你必须%service_id%在你的模板中出现至少一次。

“不建议”信息是可选的。如果不设,Symfony会显示默认信息:The "%service_id%" service is deprecated. You should stop using it, as it will soon be removed.

强烈建议你使用一个自定义的“不建议”信息,因为默认的模板太泛泛了。一个好的通知应包括:该服务何时被弱化、何时可能恢复维护,以及可用的备选服务(如果有的话)。

对于服务的装饰者(service decorators,见上文)来说,如果定义没有修改deprecated状态,decorator将从被装修者的定义中继承这个状态。

一个服务可能会有“un-deprecated”能力,仅当该服务被以PHP来声明时。

本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)