服务容器
视频教程
您更喜欢视频教程吗?查看 Symfony 基础知识视频教程系列。
你的应用程序充满了有用的对象:一个 “Mailer” 对象可以帮助你发送邮件,而另一个对象可以帮助你将内容保存到数据库。几乎所有你的应用 “做” 的事情实际上都是由这些对象之一完成的。并且每次你安装一个新的程序包,你都可以访问更多的对象!
在 Symfony 中,这些有用的对象被称为 服务 (services),每个服务都存在于一个非常特殊的被称为 服务容器 (service container) 的对象中。容器允许你集中管理对象的构建方式。它使你的生活更轻松,促进了强大的架构,并且速度超快!
获取和使用服务
当你启动一个 Symfony 应用时,你的容器已经包含了许多服务。这些服务就像工具:等待你来利用它们。在你的控制器中,你可以通过类型提示一个参数为服务的类或接口名称,来 “请求” 容器中的服务。想要 记录日志 吗?没问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Controller/ProductController.php
namespace App\Controller;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ProductController extends AbstractController
{
#[Route('/products')]
public function list(LoggerInterface $logger): Response
{
$logger->info('Look, I just used a service!');
// ...
}
}
还有哪些其他服务可用?通过运行以下命令来查找:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
$ php bin/console debug:autowiring
# this is just a *small* sample of the output...
Autowirable Types
=================
The following classes & interfaces can be used as type-hints when autowiring:
Describes a logger instance.
Psr\Log\LoggerInterface - alias:logger
Request stack that controls the lifecycle of requests.
Symfony\Component\HttpFoundation\RequestStack - alias:request_stack
RouterInterface is the interface that all Router classes must implement.
Symfony\Component\Routing\RouterInterface - alias:router.default
[...]
当你在你的控制器方法或你的 自己的服务 中使用这些类型提示时,Symfony 将自动传递给你匹配该类型的服务对象。
在整个文档中,你将看到如何使用容器中存在的许多不同的服务。
提示
实际上,容器中还有更多的服务,每个服务在容器中都有一个唯一的 ID,例如 request_stack
或 router.default
。要获取完整列表,你可以运行 php bin/console debug:container
。但是大多数时候,你不需要担心这一点。请参阅 如何选择特定的服务。请参阅 如何调试服务容器并列出服务。
在容器中创建/配置服务
你也可以将你自己的代码组织成服务。例如,假设你需要向你的用户显示一个随机的、令人愉悦的消息。如果你将此代码放在你的控制器中,则无法重用。相反,你决定创建一个新类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Service/MessageGenerator.php
namespace App\Service;
class MessageGenerator
{
public function getHappyMessage(): string
{
$messages = [
'You did it! You updated the system! Amazing!',
'That was one of the coolest updates I\'ve seen all day!',
'Great work! Keep going!',
];
$index = array_rand($messages);
return $messages[$index];
}
}
恭喜!你已经创建了你的第一个服务类!你可以立即在你的控制器中使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// src/Controller/ProductController.php
use App\Service\MessageGenerator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ProductController extends AbstractController
{
#[Route('/products/new')]
public function new(MessageGenerator $messageGenerator): Response
{
// thanks to the type-hint, the container will instantiate a
// new MessageGenerator and pass it to you!
// ...
$message = $messageGenerator->getHappyMessage();
$this->addFlash('success', $message);
// ...
}
}
当你请求 MessageGenerator
服务时,容器会构造一个新的 MessageGenerator
对象并返回它(见下面的侧边栏)。但是,如果你从不请求该服务,则永远不会构造它:节省内存和速度。作为奖励,MessageGenerator
服务只创建一次:每次你请求它时,都会返回相同的实例。
将服务限制在特定的 Symfony 环境
你可以使用 #[When]
属性仅在某些环境中将类注册为服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
use Symfony\Component\DependencyInjection\Attribute\When;
// SomeClass is only registered in the "dev" environment
#[When(env: 'dev')]
class SomeClass
{
// ...
}
// you can also apply more than one When attribute to the same class
#[When(env: 'dev')]
#[When(env: 'test')]
class AnotherClass
{
// ...
}
如果你想排除某个服务在特定环境中注册,你可以使用 #[WhenNot]
属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
use Symfony\Component\DependencyInjection\Attribute\WhenNot;
// SomeClass is registered in all environments except "dev"
#[WhenNot(env: 'dev')]
class SomeClass
{
// ...
}
// you can apply more than one WhenNot attribute to the same class
#[WhenNot(env: 'dev')]
#[WhenNot(env: 'test')]
class AnotherClass
{
// ...
}
7.2
#[WhenNot]
属性在 Symfony 7.2 中引入。
将服务/配置注入到服务中
如果你需要从 MessageGenerator
内部访问 logger
服务怎么办?没问题!创建一个带有 $logger
参数的 __construct()
方法,该参数具有 LoggerInterface
类型提示。将其设置在一个新的 $logger
属性上,并在稍后使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// src/Service/MessageGenerator.php
namespace App\Service;
use Psr\Log\LoggerInterface;
class MessageGenerator
{
public function __construct(
private LoggerInterface $logger,
) {
}
public function getHappyMessage(): string
{
$this->logger->info('About to find a happy message!');
// ...
}
}
就是这样!容器会自动知道在实例化 MessageGenerator
时传递 logger
服务。它是如何知道做到这一点的?自动装配 (Autowiring)。关键是在你的 __construct()
方法中的 LoggerInterface
类型提示以及 services.yaml
中的 autowire: true
配置。当你类型提示一个参数时,容器将自动找到匹配的服务。如果找不到,你将看到一个清晰的异常,并提供有用的建议。
顺便说一句,这种将依赖项添加到你的 __construct()
方法的方法称为依赖注入 (dependency injection)。
你应该如何知道为类型提示使用 LoggerInterface
?你可以阅读你正在使用的任何功能的文档,或者通过运行以下命令获取可自动装配的类型提示列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
$ php bin/console debug:autowiring
# this is just a *small* sample of the output...
Describes a logger instance.
Psr\Log\LoggerInterface - alias:monolog.logger
Request stack that controls the lifecycle of requests.
Symfony\Component\HttpFoundation\RequestStack - alias:request_stack
RouterInterface is the interface that all Router classes must implement.
Symfony\Component\Routing\RouterInterface - alias:router.default
[...]
处理多个服务
假设你还想在每次站点更新时通过电子邮件通知站点管理员。为此,你创建一个新类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
// src/Service/SiteUpdateManager.php
namespace App\Service;
use App\Service\MessageGenerator;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
class SiteUpdateManager
{
public function __construct(
private MessageGenerator $messageGenerator,
private MailerInterface $mailer,
) {
}
public function notifyOfSiteUpdate(): bool
{
$happyMessage = $this->messageGenerator->getHappyMessage();
$email = (new Email())
->from('[email protected]')
->to('[email protected]')
->subject('Site update just happened!')
->text('Someone just updated the site. We told them: '.$happyMessage);
$this->mailer->send($email);
// ...
return true;
}
}
这需要 MessageGenerator
和 Mailer
服务。没问题,我们通过类型提示它们的类和接口名称来请求它们!现在,这个新服务已准备好使用。例如,在控制器中,你可以类型提示新的 SiteUpdateManager
类并使用它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
// src/Controller/SiteController.php
namespace App\Controller;
use App\Service\SiteUpdateManager;
// ...
class SiteController extends AbstractController
{
public function new(SiteUpdateManager $siteUpdateManager): Response
{
// ...
if ($siteUpdateManager->notifyOfSiteUpdate()) {
$this->addFlash('success', 'Notification mail was sent successfully.');
}
// ...
}
}
感谢自动装配和你 __construct()
中的类型提示,容器会创建 SiteUpdateManager
对象并传递给它正确的参数。在大多数情况下,这都能完美地工作。
手动连接参数
但是,在少数情况下,服务的参数无法自动装配。例如,假设你想使管理员电子邮件可配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
// src/Service/SiteUpdateManager.php
// ...
class SiteUpdateManager
{
// ...
public function __construct(
private MessageGenerator $messageGenerator,
private MailerInterface $mailer,
+ private string $adminEmail
) {
}
public function notifyOfSiteUpdate(): bool
{
// ...
$email = (new Email())
// ...
- ->to('[email protected]')
+ ->to($this->adminEmail)
// ...
;
// ...
}
}
如果你进行此更改并刷新,你将看到一个错误:
无法自动装配服务 "App\Service\SiteUpdateManager":方法 "__construct()" 的参数 "$adminEmail" 必须具有类型提示或显式给定值。
这很有道理!容器无法知道你想在此处传递什么值。没问题!在你的配置中,你可以显式设置此参数:
1 2 3 4 5 6 7 8 9 10 11 12 13
# config/services.yaml
services:
# ... same as before
# same as before
App\:
resource: '../src/'
exclude: '../src/{DependencyInjection,Entity,Kernel.php}'
# explicitly configure the service
App\Service\SiteUpdateManager:
arguments:
$adminEmail: '[email protected]'
因此,容器将在创建 SiteUpdateManager
服务时将 [email protected]
传递给 __construct
的 $adminEmail
参数。其他参数仍将自动装配。
但是,这不是很脆弱吗?幸运的是,不是!如果你将 $adminEmail
参数重命名为其他名称 - 例如 $mainEmail
- 则在重新加载下一页时,你将获得一个清晰的异常(即使该页面未使用此服务)。
服务参数
除了保存服务对象外,容器还保存配置,称为 参数 (parameters)。关于 Symfony 配置的主要文章详细解释了 配置参数,并显示了它们的所有类型(字符串、布尔值、数组、二进制和 PHP 常量参数)。
但是,还有另一种与服务相关的参数类型。在 YAML 配置中,任何以 @
开头的字符串都被视为服务的 ID,而不是常规字符串。在 XML 配置中,为参数使用 type="service"
类型,在 PHP 配置中使用 service()
函数:
1 2 3 4 5 6 7 8 9 10 11
# config/services.yaml
services:
App\Service\MessageGenerator:
arguments:
# this is not a string, but a reference to a service called 'logger'
- '@logger'
# if the value of a string argument starts with '@', you need to escape
# it by adding another '@' so Symfony doesn't consider it a service
# the following example would be parsed as the string '@securepassword'
# - '@@securepassword'
使用容器的参数访问器方法可以轻松处理容器参数:
1 2 3 4 5 6 7 8
// checks if a parameter is defined (parameter names are case-sensitive)
$container->hasParameter('mailer.transport');
// gets value of a parameter
$container->getParameter('mailer.transport');
// adds a new parameter
$container->setParameter('mailer.transport', 'sendmail');
警告
使用的 .
表示法是 Symfony 约定,使参数更易于阅读。参数是扁平的键值元素,它们不能组织成嵌套数组。
注意
你只能在容器编译之前设置参数,而不能在运行时设置。要了解有关编译容器的更多信息,请参阅 编译容器。
选择特定的服务
先前创建的 MessageGenerator
服务需要一个 LoggerInterface
参数:
1 2 3 4 5 6 7 8 9 10 11 12 13
// src/Service/MessageGenerator.php
namespace App\Service;
use Psr\Log\LoggerInterface;
class MessageGenerator
{
public function __construct(
private LoggerInterface $logger,
) {
}
// ...
}
但是,容器中有多个服务实现了 LoggerInterface
,例如 logger
、monolog.logger.request
、monolog.logger.php
等。容器如何知道使用哪一个?
在这些情况下,容器通常被配置为自动选择其中一个服务 - 在这种情况下为 logger
(在 自动定义服务依赖项 (自动装配) 中阅读更多关于原因的信息)。但是,你可以控制这一点并传入不同的记录器:
1 2 3 4 5 6 7 8 9 10 11
# config/services.yaml
services:
# ... same code as before
# explicitly configure the service
App\Service\MessageGenerator:
arguments:
# the '@' symbol is important: that's what tells the container
# you want to pass the *service* whose id is 'monolog.logger.request',
# and not just the *string* 'monolog.logger.request'
$logger: '@monolog.logger.request'
这告诉容器,__construct
的 $logger
参数应使用 ID 为 monolog.logger.request
的服务。
要获得可以与自动装配一起使用的可能的记录器服务列表,请运行:
1
$ php bin/console debug:autowiring logger
要获得容器中所有可能的服务的完整列表,请运行:
1
$ php bin/console debug:container
移除服务
如果需要,可以从服务容器中移除服务。例如,这对于使服务在某些 配置环境 中不可用很有用(例如在 test
环境中):
1 2 3 4 5 6 7 8 9 10
// config/services_test.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
use App\RemovedService;
return function(ContainerConfigurator $containerConfigurator) {
$services = $containerConfigurator->services();
$services->remove(RemovedService::class);
};
现在,容器在 test
环境中将不包含 App\RemovedService
。
注入闭包作为参数
可以将可调用对象作为服务的参数注入。让我们为我们的 MessageGenerator
构造函数添加一个参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// src/Service/MessageGenerator.php
namespace App\Service;
use Psr\Log\LoggerInterface;
class MessageGenerator
{
private string $messageHash;
public function __construct(
private LoggerInterface $logger,
callable $generateMessageHash,
) {
$this->messageHash = $generateMessageHash();
}
// ...
}
现在,我们将添加一个新的可调用服务来生成消息哈希:
1 2 3 4 5 6 7 8 9 10
// src/Hash/MessageHashGenerator.php
namespace App\Hash;
class MessageHashGenerator
{
public function __invoke(): string
{
// Compute and return a message hash
}
}
我们的配置如下所示:
1 2 3 4 5 6 7 8 9
# config/services.yaml
services:
# ... same code as before
# explicitly configure the service
App\Service\MessageGenerator:
arguments:
$logger: '@monolog.logger.request'
$generateMessageHash: !closure '@App\Hash\MessageHashGenerator'
另请参阅
可以使用 自动装配 及其专用属性来注入闭包。
按名称或类型绑定参数
你还可以使用 bind
关键字按名称或类型绑定特定参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# config/services.yaml
services:
_defaults:
bind:
# pass this value to any $adminEmail argument for any service
# that's defined in this file (including controller arguments)
$adminEmail: '[email protected]'
# pass this service to any $requestLogger argument for any
# service that's defined in this file
$requestLogger: '@monolog.logger.request'
# pass this service for any LoggerInterface type-hint for any
# service that's defined in this file
Psr\Log\LoggerInterface: '@monolog.logger.request'
# optionally you can define both the name and type of the argument to match
string $adminEmail: '[email protected]'
Psr\Log\LoggerInterface $requestLogger: '@monolog.logger.request'
iterable $rules: !tagged_iterator app.foo.rule
# ...
通过将 bind
键放在 _defaults
下,你可以为在此文件中定义的任何服务指定任何参数的值!你可以按名称(例如 $adminEmail
)、按类型(例如 Psr\Log\LoggerInterface
)或两者(例如 Psr\Log\LoggerInterface $requestLogger
)绑定参数。
bind
配置也可以应用于特定服务或在 一次加载多个服务 时应用。
抽象服务参数
有时,某些服务参数的值无法在配置文件中定义,因为它们是在运行时使用 编译器传递 或 程序包扩展 计算的。
在这些情况下,你可以使用 abstract
参数类型来至少定义参数的名称和有关其用途的简短描述:
1 2 3 4 5 6 7 8 9
# config/services.yaml
services:
# ...
App\Service\MyService:
arguments:
$rootNamespace: !abstract 'should be defined by Pass'
# ...
如果你在运行时不替换抽象参数的值,则会抛出 RuntimeException
,并显示类似 Argument "$rootNamespace" of service "App\Service\MyService" is abstract: should be defined by Pass.
的消息。
autowire 选项
上面,services.yaml
文件在 _defaults
部分中具有 autowire: true
,以便它应用于该文件中定义的所有服务。使用此设置,你可以在服务的 __construct()
方法中类型提示参数,并且容器将自动传递给你正确的参数。整个条目都是围绕自动装配编写的。
有关自动装配的更多详细信息,请查看 自动定义服务依赖项 (自动装配)。
autoconfigure 选项
上面,services.yaml
文件在 _defaults
部分中具有 autoconfigure: true
,以便它应用于该文件中定义的所有服务。使用此设置,容器将根据你的服务的类自动将某些配置应用于你的服务。这主要用于自动标记 (auto-tag) 你的服务。
例如,要创建 Twig 扩展,你需要创建一个类,将其注册为服务,并使用 twig.extension
标记 它。
但是,使用 autoconfigure: true
,你不需要标签。实际上,如果你正在使用 默认的 services.yaml 配置,你什么都不需要做:服务将自动加载。然后,autoconfigure
将为你添加 twig.extension
标签,因为你的类实现了 Twig\Extension\ExtensionInterface
。并且由于 autowire
,你甚至可以添加构造函数参数而无需任何配置。
自动配置也适用于属性。某些属性(如 AsMessageHandler、AsEventListener 和 AsCommand)已注册用于自动配置。任何使用这些属性的类都将应用标签。
Linting 服务定义
lint:container
命令执行额外的检查,以确保容器已正确配置。在将应用程序部署到生产环境之前(例如,在你的持续集成服务器中)运行此命令很有用:
1 2 3 4 5
$ php bin/console lint:container
# optionally, you can force the resolution of environment variables;
# the command will fail if any of those environment variables are missing
$ php bin/console lint:container --resolve-env-vars
7.2
--resolve-env-vars
选项在 Symfony 7.2 中引入。
每当编译容器时执行这些检查可能会损害性能。这就是为什么它们在称为 CheckTypeDeclarationsPass
和 CheckAliasValidityPass
的 编译器传递 中实现的原因,这些编译器传递默认情况下禁用,仅在执行 lint:container
命令时启用。如果你不介意性能损失,则可以在你的应用程序中启用这些编译器传递。
7.1
CheckAliasValidityPass
编译器传递在 Symfony 7.1 中引入。
公共与私有服务
默认情况下,每个定义的服务都是私有的。当服务是私有时,你无法使用 $container->get()
从容器直接访问它。作为最佳实践,你应仅创建私有服务,并且应使用依赖注入而不是使用 $container->get()
来获取服务。
如果你需要延迟获取服务,则应考虑使用 服务定位器,而不是使用公共服务。
但是,如果你确实需要使服务公开,请覆盖 public
设置:
1 2 3 4 5 6 7
# config/services.yaml
services:
# ... same code as before
# explicitly configure the service
App\Service\PublicService:
public: true
也可以借助 #[Autoconfigure]
属性将服务定义为公共的。此属性必须直接用于你要配置的服务的类上:
1 2 3 4 5 6 7 8 9 10
// src/Service/PublicService.php
namespace App\Service;
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;
#[Autoconfigure(public: true)]
class PublicService
{
// ...
}
使用 resource 一次性导入多个服务
你已经看到可以使用 resource
键一次性导入多个服务。例如,默认的 Symfony 配置包含以下内容:
1 2 3 4 5 6 7 8 9
# config/services.yaml
services:
# ... same as before
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude: '../src/{DependencyInjection,Entity,Kernel.php}'
这可以用于快速使许多类作为服务可用,并应用一些默认配置。每个服务的 id
是其完全限定的类名。你可以通过在其下方使用其 ID(类名)来覆盖任何导入的服务(例如,请参阅 如何手动连接参数)。如果你覆盖服务,则任何选项(例如 public
)都不会从导入中继承(但是被覆盖的服务仍然从 _defaults
继承)。
您也可以 exclude
某些路径。这是可选的,但会在 dev
环境中略微提高性能:排除的路径不会被跟踪,因此修改它们不会导致容器重建。
注意
等等,这是否意味着 src/ 中的每个类都被注册为服务?甚至是模型类?实际上,并非如此。只要您将导入的服务保持为 私有,src/
中未显式用作服务的所有类都会自动从最终容器中移除。实际上,导入意味着所有类“都可以用作服务”,而无需手动配置。
使用相同命名空间的多个服务定义
如果您使用 YAML 配置格式定义服务,PHP 命名空间将用作每个配置的键,因此您无法为同一命名空间下的类定义不同的服务配置
1 2 3 4 5
# config/services.yaml
services:
App\Domain\:
resource: '../src/Domain/*'
# ...
为了拥有多个定义,请添加 namespace
选项,并使用任何唯一字符串作为每个服务配置的键
1 2 3 4 5 6 7 8 9 10 11
# config/services.yaml
services:
command_handlers:
namespace: App\Domain\
resource: '../src/Domain/*/CommandHandler'
tags: [command_handler]
event_subscribers:
namespace: App\Domain\
resource: '../src/Domain/*/EventSubscriber'
tags: [event_subscriber]
显式配置服务和参数
自动加载服务 和 自动装配 是可选的。即使您使用它们,在某些情况下您可能也想手动装配服务。例如,假设您想为 SiteUpdateManager
类注册 2 个服务 - 每个服务具有不同的管理员电子邮件。在这种情况下,每个服务都需要有一个唯一的服务 ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
# config/services.yaml
services:
# ...
# this is the service's id
site_update_manager.superadmin:
class: App\Service\SiteUpdateManager
# you CAN still use autowiring: we just want to show what it looks like without
autowire: false
# manually wire all arguments
arguments:
- '@App\Service\MessageGenerator'
- '@mailer'
- '[email protected]'
site_update_manager.normal_users:
class: App\Service\SiteUpdateManager
autowire: false
arguments:
- '@App\Service\MessageGenerator'
- '@mailer'
- '[email protected]'
# Create an alias, so that - by default - if you type-hint SiteUpdateManager,
# the site_update_manager.superadmin will be used
App\Service\SiteUpdateManager: '@site_update_manager.superadmin'
在这种情况下,注册了 两个 服务:site_update_manager.superadmin
和 site_update_manager.normal_users
。由于别名的存在,如果您类型提示 SiteUpdateManager
,则第一个服务 (site_update_manager.superadmin
) 将被传递。
如果您想传递第二个服务,您需要 手动装配服务 或创建一个命名的 自动装配别名。
警告
如果您没有创建别名并且 从 src/ 加载所有服务,那么将创建 三个 服务(自动服务 + 您的两个服务),并且当您类型提示 SiteUpdateManager
时,默认情况下将传递自动加载的服务。这就是为什么创建别名是一个好主意。
当使用 PHP 闭包来配置服务时,可以通过向闭包添加名为 $env
的字符串参数来自动注入当前环境值
1 2 3 4 5 6 7
// config/packages/my_config.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return function(ContainerConfigurator $containerConfigurator, string $env): void {
// `$env` is automatically filled in, so you can configure your
// services depending on which environment you're on
};
为函数式接口生成适配器
函数式接口是只有一个方法的接口。它们在概念上与闭包非常相似,只是它们唯一的方法有一个名称。此外,它们可以用作代码中的类型提示。
AutowireCallable 属性可用于为函数式接口生成适配器。假设您有以下函数式接口
1 2 3 4 5 6 7
// src/Service/MessageFormatterInterface.php
namespace App\Service;
interface MessageFormatterInterface
{
public function format(string $message, array $parameters): string;
}
您还有一个服务定义了许多方法,其中一种方法与先前接口的 format()
方法相同
1 2 3 4 5 6 7 8 9 10 11 12
// src/Service/MessageUtils.php
namespace App\Service;
class MessageUtils
{
// other methods...
public function format(string $message, array $parameters): string
{
// ...
}
}
由于 #[AutowireCallable]
属性,您现在可以将此 MessageUtils
服务作为函数式接口实现注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
namespace App\Service\Mail;
use App\Service\MessageFormatterInterface;
use App\Service\MessageUtils;
use Symfony\Component\DependencyInjection\Attribute\AutowireCallable;
class Mailer
{
public function __construct(
#[AutowireCallable(service: MessageUtils::class, method: 'format')]
private MessageFormatterInterface $formatter
) {
}
public function sendMail(string $message, array $parameters): string
{
$formattedMessage = $this->formatter->format($message, $parameters);
// ...
}
}
除了使用 #[AutowireCallable]
属性外,您还可以通过配置为函数式接口生成适配器
1 2 3 4 5 6 7 8
# config/services.yaml
services:
# ...
app.message_formatter:
class: App\Service\MessageFormatterInterface
from_callable: [!service {class: 'App\Service\MessageUtils'}, 'format']
这样做,Symfony 将生成一个类(也称为适配器),该类实现 MessageFormatterInterface
,它会将 MessageFormatterInterface::format()
的调用转发到您的底层服务方法 MessageUtils::format()
,并带有其所有参数。