[英]Is it possible to access a Symfony service's class name dynamically in yaml definitions (services.yaml)?
问题
我有很多类是父母 class 的孩子。
我用来实例化这些孩子的工厂采用一个参数,一个字符串,它是孩子的 class 名称,因此工厂可以知道要构建什么。
希望
我真的很想一举解决所有这些孩子的自动装配问题。 我知道在services.yaml
我可以通过通配符资源递归地定义类的自动装配配置,例如:
...
Namespace\:
resource: '..namespace/foo/*'
# configuration for all classes in the 'foo' directory
autowire: true
public: true
但是,对于我需要使用接受其 class 名称的工厂方法实例化的子目录来说,这似乎不太可能。
我希望能够做的是类似于这样的事情,其中“$1”相当于匹配通配符的字符串,就像许多基于正则表达式的解决方案允许的那样(包括一些通过 Symfony 本身可用的字符串,如路由):
...
Domain\Children\:
resource: '../domain/children/*'
factory: ['Domain\Factory', 'getChild']
arguments: 'Domain\Children\$1'
但是,这可以理解是行不通的。
我知道有一些以“表达式”和!tagged_iterator
(见这里)形式的动态解决方案,所以我怀疑可能有一些东西可以做我正在寻找的东西,因为这两个示例的文档相当疏散。 我一直无法找到一种方法来动态获取 class 的名称作为其服务定义的一部分,希望它存在,但我找不到它。
Another thing I tried was giving the child class a function that would instantiate it as expected, accepting a parameter of the factory class, but I couldn't find a way to define a relative function to be used in place of a factory. 除了类的构造函数之外,似乎使用任何东西的唯一方法是显式声明 class 和 function 对。
澄清
我不接受任何涉及重构这些类相对于我在这里指的工厂的构建方式的答案。 这个问题不是“这个设计是个好主意吗”,我们已经过去了——这是我坚持的设计。
问题总结
在定义通用自动装配配置时,有什么方法可以实现动态引用 class 的名称(在 yaml 中没有显式字符串)的目标?
我不知道如何使用 yaml 来做这种事情,但编译通过很容易,可能更容易理解。 我使用新的 Symfony 6 应用程序开发了它,但它在早期版本上应该可以正常工作。 默认 services.yaml 文件完全没有更改。
从域对象开始,以确保我们在同一页面上。
namespace App\Domain;
abstract class MyParent {}
class Child1 extends MyParent {}
class Child2 extends MyParent {}
class MyFactory
{
public function getChild(string $childClass) : MyParent
{
// Just ignore the 'something' for now
return new $childClass('something');
}
}
此时,如果您运行bin/console debug:container Child1
,您将看到有 Child1 服务,但它没有使用 MyFactory。
现在我们添加编译通道:
# src/Kernel.php
# implement CompilerPassInterface
class Kernel extends BaseKernel implements CompilerPassInterface
{
use MicroKernelTrait;
// Tag all the child classes
protected function build(ContainerBuilder $container)
{
$container->registerForAutoconfiguration(MyParent::class)
->addTag('my.child');
}
public function process(ContainerBuilder $container)
{
$factoryReference = new Reference(MyFactory::class);
// Grab all the child services
$childClasses = array_keys($container->findTaggedServiceIds('my.child'));
foreach ($childClasses as $childClass) {
// And replace with a factory service
$definition = new Definition($childClass);
$definition->setFactory([$factoryReference,'getChild'])
->setArgument(0,$childClass);
$container->setDefinition($childClass,$definition);
}
//dump($childClasses);
}
}
现在,当我们检查 Child1 服务时,我们可以看到它像我们想要的那样调用了 MyFactory::getChild。 我做了一个命令来测试注入:
class ChildCommand extends Command
{
public function __construct(private Child1 $child1, private Child2 $child2)
{
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->success('Child1 ' . get_class($this->child1));
$io->success('Child2 ' . get_class($this->child2));
return Command::SUCCESS;
}
}
这一切似乎都按预期工作。
我有点担心构造函数 arguments。 我假设您的孩子班可能有一些,我不确定自动装配是否会导致问题。 所以我加了一个:
class Child2 extends MyParent
{
public function __construct(private string $something)
{
}
}
再一次,这一切似乎都奏效了。 当然,注入任何必要的依赖项取决于您的工厂 class。
享受
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.