如何在 Bundle 内部加载服务配置
Bundle 创建的服务不是在应用程序使用的主 config/services.yaml
文件中定义的,而是在 Bundle 本身中定义的。本文解释了如何使用 Bundle 目录结构创建和加载服务文件。
有两种不同的方法可以做到这一点
- 在主 Bundle 类中加载你的服务:这对于新 Bundle 以及遵循推荐目录结构的 Bundle 来说是推荐的做法;
- 创建一个扩展类来加载服务配置文件:这是传统的做法,但现在仅推荐给遵循旧目录结构的 Bundle。
直接在你的 Bundle 类中加载服务
在扩展 AbstractBundle 类的 Bundle 中,你可以定义 loadExtension() 方法,从配置文件中加载服务定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
// ...
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\HttpKernel\Bundle\AbstractBundle;
class AcmeHelloBundle extends AbstractBundle
{
public function loadExtension(array $config, ContainerConfigurator $container, ContainerBuilder $builder): void
{
// load an XML, PHP or YAML file
$container->import('../config/services.xml');
// you can also add or replace parameters and services
$container->parameters()
->set('acme_hello.phrase', $config['phrase'])
;
if ($config['scream']) {
$container->services()
->get('acme_hello.printer')
->class(ScreamingPrinter::class)
;
}
}
}
此方法的工作方式类似于下面解释的 Extension::load()
方法,但它使用了一个新的更简单的 API 来定义和导入服务配置。
注意
与 Extension::load()
中的 $configs
参数相反,$config
参数已经由 AbstractBundle
合并和处理。
注意
loadExtension()
仅在编译时调用。
创建扩展类
这是在 Bundle 中加载服务定义的传统方法。对于新的 Bundle,建议在主 Bundle 类中加载你的服务,但传统的创建扩展类的方法仍然有效。
依赖注入扩展被定义为一个遵循以下约定的类(稍后你将学习如何在需要时跳过它们)
- 它必须位于 Bundle 的
DependencyInjection
命名空间中; - 它必须实现 ExtensionInterface,这通常通过扩展 Extension 类来实现;
- 名称等于 Bundle 名称,其中
Bundle
后缀替换为Extension
(例如,AcmeBundle 的扩展类将被称为AcmeExtension
,而 AcmeHelloBundle 的扩展类将被称为AcmeHelloExtension
)。
这就是 AcmeHelloBundle 的扩展应该如何 виглядати
1 2 3 4 5 6 7 8 9 10 11 12 13
// src/DependencyInjection/AcmeHelloExtension.php
namespace Acme\HelloBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
class AcmeHelloExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container): void
{
// ... you'll load the files here later
}
}
手动注册扩展类
当不遵循约定时,你将必须手动注册你的扩展。为此,你应该覆盖 Bundle::getContainerExtension() 方法以返回扩展的实例
1 2 3 4 5 6 7 8 9 10 11
// ...
use Acme\HelloBundle\DependencyInjection\UnconventionalExtensionClass;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
class AcmeHelloBundle extends Bundle
{
public function getContainerExtension(): ?ExtensionInterface
{
return new UnconventionalExtensionClass();
}
}
此外,当新的扩展类名不遵循命名约定时,你还必须覆盖 Extension::getAlias() 方法以返回正确的 DI 别名。DI 别名是在容器中引用 Bundle 的名称(例如,在 config/packages/
文件中)。默认情况下,这是通过删除 Extension
后缀并将类名转换为下划线来完成的(例如,AcmeHelloExtension
的 DI 别名是 acme_hello
)。
使用 load()
方法
在 load()
方法中,所有与此扩展相关的服务和参数都将被加载。此方法不会获取实际的容器实例,而是获取副本。此容器仅具有来自实际容器的参数。加载服务和参数后,副本将合并到实际容器中,以确保所有服务和参数也添加到实际容器中。
在 load()
方法中,你可以使用 PHP 代码来注册服务定义,但更常见的是将这些定义放在配置文件中(使用 YAML、XML 或 PHP 格式)。
例如,假设你的 Bundle 的 config/
目录中有一个名为 services.xml
的文件,你的 load()
方法如下所示
1 2 3 4 5 6 7 8 9 10 11 12
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
// ...
public function load(array $configs, ContainerBuilder $container): void
{
$loader = new XmlFileLoader(
$container,
new FileLocator(__DIR__.'/../../config')
);
$loader->load('services.xml');
}
其他可用的加载器是 YamlFileLoader
和 PhpFileLoader
。
使用配置来更改服务
Extension 也是处理该特定 Bundle 的配置的类(例如,config/packages/<bundle_alias>.yaml
中的配置)。要了解更多信息,请参阅“如何为 Bundle 创建友好的配置”文章。
添加要编译的类
Bundle 可以提示 Symfony 哪些类包含注解,以便在生成应用程序缓存以提高整体性能时编译它们。在 addAnnotatedClassesToCompile()
方法中定义要编译的带注解的类列表
1 2 3 4 5 6 7 8 9 10 11 12 13
public function load(array $configs, ContainerBuilder $container): void
{
// ...
$this->addAnnotatedClassesToCompile([
// you can define the fully qualified class names...
'Acme\\BlogBundle\\Controller\\AuthorController',
// ... but glob patterns are also supported:
'Acme\\BlogBundle\\Form\\**',
// ...
]);
}
注意
如果某个类从其他类扩展,则其所有父类都会自动包含在要编译的类列表中。
模式使用 Composer 生成的 classmap 转换为实际的类命名空间。因此,在使用这些模式之前,你必须执行 Composer 的 dump-autoload
命令来生成完整的 classmap。
警告
当要编译的类使用 __DIR__
或 __FILE__
常量时,不能使用此技术,因为当从 classes.php
文件加载这些类时,它们的值会发生变化。