如何在Bundle内加载服务配置

2.8 版本
维护中的版本

在 Symfony 中,你会发现自己使用了很多的服务。这些服务可以在你程序的 app/config/ 目录中被注册。但当你想要解耦这个 bundle 以便它们用在其他项目中时,你就会想让服务配置存于 bundle 本身。本文将教会你如何实现。

创建一个Extension类(扩展类) 

为了加载服务配置,你要为你的 bundle 创建一个依赖注入(DI)扩展。这个类有一些约定,以便能够自动被检测到。但稍后你会看到如何将它更改为你自己偏好的样子。默认情况下,Extension 类必须遵守以下约定:

  • 它必须位于 bundle 的 DependencyInjection 命名空间下;

  • 它的名称要和 bundle 名称一样,只是把 Bundle 后缀换成了 Extension(例如 AppBundle 的 Extension 类就会叫做 AppExtension,同时 AcmeHelloBundle 就会被叫做 AcmeHelloExtension)。

Extension 类应该实现 ExtensionInterface 接口,但通常你只需要继承 Extension 抽象类:

1
2
3
4
5
6
7
8
9
10
11
12
13
// src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
 
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
 
class AcmeHelloExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        // ... you'll load the files here later / 以后可在此处加载文件
    }
}

手动注册 Extension 类 

当没有遵守这些约定时,你必须手动注册你的 Extension 类。要这么做到,你应该覆写 Bundle::getContainerExtension() 方法来返回 Extension 实例:

1
2
3
4
5
6
7
8
9
10
// ...
use Acme\HelloBundle\DependencyInjection\UnconventionalExtensionClass;
 
class AcmeHelloBundle extends Bundle
{
    public function getContainerExtension()
    {
        return new UnconventionalExtensionClass();
    }
}

由于新的 Extension 类名没有遵循命名约定,你同时需要覆写 Extension::getAlias()来返回正确的 DI 别名。DI 别名是用来标识容器中的 bundle 的(即,在 app/config/config.yml 文件中)。默认时可以通过移除 Extension 后缀并把类名转化成下划线分隔来完成(例如,AcmeHelloExtension 的别名是 acme_hello)。

使用load()方法 

load() 方法里,所有与扩展相关的服务和参数都将被加载。这个方法不获取真正的容器实例,只是一个副本。这个容器只包含从实际容器中取来的参数。在加载服务和参数之后,此副本就会合并到实际容器中,以确保全部的服务和参数都被添加到实际容器之中。

load() 方法里,你可以使用 PHP 代码来注册服务定义,但更常见的做法是把这些定义放到配置文件中(可使用 Yaml, XML 或者 PHP)。幸运的是,你可以在 extension 中使用 file loader!

例如,假设你有一个名为 services.xml 的文件位于你 bundle 中的 Resources/config 目录,你的 load() 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\Config\FileLocator;
 
// ...
public function load(array $configs, ContainerBuilder $container)
{
    $loader = new XmlFileLoader(
        $container,
        new FileLocator(__DIR__.'/../Resources/config')
    );
    $loader->load('services.xml');
}

可用的加载器有 YamlFileLoaderPhpFileLoaderIniFileLoader

Note

IniFileLoader 只能用于加载参数,且只能将参数作为字符串加载。

Caution

如果你删除了包含服务定义的默认文件(如, app/config/services.yml),也要确保把它从 app/config/config.yml 中的 imports 键下给删除。

使用配置去改变这个服务 

Extension 类也是处理特定 bundle 之配置的类(如 app/config/config.yml 中的配置信息)。更多内容参考 如何为Bundle创建友好的配置

添加类并编译 

Note

addClassesToCompile() 方法在 Symfony 3.3 不建议再使用,且会在 Symfony 4.0 中删除。如果你要使用本方法并与 Symfony 4.0 能够兼容,调用之前先检查这个方法是否存在。

Symfony 在缓存目录创建了一个很大的 classes.php 文件,聚合了每次请求中都要用到的 PHP 类的内容。它减少了 I/O 吞吐操作并提高了程序性能。

3.2新增 addAnnotatedClassesToCompile() 方法自 Symfony 3.2 起被引入。

你的 bundle 也能添加他们自己的类到这个文件,多亏了 addClassesToCompile() 方法和 addAnnotatedClassesToCompile() 方法。把要编译的类定义为一个 FQCN 完整类名的数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use AppBundle\Manager\UserManager;
use AppBundle\Utils\Slugger;
 
// ...
public function load(array $configs, ContainerBuilder $container)
{
    // ...
 
    // this method can't compile classes that contain PHP annotations
    // 此方法不可编译包含了 PHP annotation 的类
    $this->addClassesToCompile(array(
        UserManager::class,
        Slugger::class,
        // ...
    ));
 
    // add here only classes that contain PHP annotations
    // 只可把包含了 PHP annotation 的类添加到此处
    $this->addAnnotatedClassesToCompile(array(
        'AppBundle\\Controller\\DefaultController',
        // ...
    ));
}

Note

如果有一些类是从其他的类继承过来的,它们的父类都将自动地包含到待编译的类的列表。

3.2新增 使用 patterns 来添加待编译类的选项自 Symfony 3.2 起被引入。

待编译的类可通过使用 file path patterns 进行添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ...
public function load(array $configs, ContainerBuilder $container)
{
    // ...
 
    $this->addClassesToCompile(array(
        '**Bundle\\Manager\\',
        // ...
    ));
 
    $this->addAnnotatedClassesToCompile(array(
        '**Bundle\\Controller\\',
        // ...
    ));
}

利用 Composer 所生成的 classmap,patterns 被转换成真实类的命名空间。因此,在使用这些 patterns 之前,你必须执行 Composer 的 dump-autoload 以生成完整的 classmap。

Caution

这技巧不可用在使用了 __DIR____FILE__ 常量的待编译类中,因为它们的值将在这些类从 classes.php 文件中加载时发生改变。

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

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