将动态参数传递给 Symfony 中的服务工厂
Passing dynamic arguments to service factory in Symfony
我正在将 Symfony 集成到一个旧的应用程序中,该应用程序具有自己的基于 PSR-11 的依赖项容器。一直在寻找将 DI 容器合并到 Symfony 使用的解决方案,但一无所获。为了让它发挥作用,我提出了一个我不喜欢的“hacky”解决方案。
我创建了这个 class。它在其中创建了一个旧 DI 容器的实例:
class OldAppServiceFactory
{
private ContainerInterface $container;
public function __construct()
{
$this->container = OldContainerFactory::create();
}
public function factory(string $className)
{
return $this->container->get($className);
}
}
并向 services.yaml
添加了正确的条目:
oldapp.service_factory:
class: Next\Service\LeonContainer\LeonServiceFactory
OldApp\Repository\Repository1:
factory: ['@oldapp.service_factory', 'factory']
arguments:
- 'OldApp\Repository\Repository1'
OldApp\Repository\Repository2:
factory: ['@oldapp.service_factory', 'factory']
arguments:
- 'OldApp\Repository\Repository2'
OldApp\configuration\ConfigurationProviderInterface:
factory: ['@oldapp.service_factory', 'factory']
arguments:
- 'OldApp\configuration\ConfigurationProviderInterface'
通过上述 hack,将那些 classes 投入服务 class 构造函数工作。不幸的是,它看起来很糟糕,并且用更多这样的存储库扩展它会很痛苦(尤其是当有 50 个时)。是否有可能在 services.yaml
中实现这样的目标?
OldApp\Repository\:
factory: ['@oldapp.service_factory', 'factory']
arguments:
- << PASS FQCN HERE >>
这将使我在 services.yaml
中只有一个条目用于旧应用程序的单个命名空间。
但是,也许我的问题还有其他解决方案?一直在尝试配置 Kernel.php
和 prepareContainer(...)
方法,但我也一无所获,因为旧的依赖项位于一个返回数组的 PHP 文件中:
return array [
RepositoryMetadataCache::class => static fn () => RepositoryMetadataCache::createFromCacheFile(),
EntityCollection::class => autowire(EntityCollection::class),
'Model\Repository\*' => static function (ContainerInterface $container, RequestedEntry $entry) { ... }
];
您可以使用自定义编译器传递轻松完成此操作。
首先通过加载它们存在的目录来标记所有旧存储库类:
OldApp\Repository\:
resource: '../src/OldApp/Repository/*'
autowire: false
autoconfigure: false
tags: ['oldapp_repository']
(我认为您可能还需要从默认的自动服务加载中排除 src/OldApp
。例如:
App\:
resource: '../src/*'
exclude: '../src/{OldApp/Repository,DependencyInjection,Entity,Tests,Kernel.php}'
...但我不是 100% 确定,测试这个)。
然后创建一个编译器通道来遍历标签并为每个标签定义一个工厂:
class OldAppRepositoryCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$taggedServices = $container->findTaggedServiceIds('oldapp_repository');
foreach ($taggedServices as $serviceId => $tags) {
$definition = $container->getDefinition($serviceId);
$definition
->setFactory([new Reference('oldapp.service_factory'), 'factory'])
->addArgument($serviceId);
}
}
}
并在您的应用程序内核 build()
方法中添加编译器传递:
// src/Kernel.php
namespace App;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
// ...
class Kernel extends BaseKernel
{
// ...
protected function build(ContainerBuilder $container): void
{
$container->addCompilerPass(new OldAppRepositoryCompilerPass());
}
}
目前无法对此进行正确测试,但这应该会让您朝着正确的方向前进。有关其他详细信息,请查看文档:
您可以检查 this example repo 以上内容的实施和工作地点。在此 repo 中,OldApp
命名空间在 App
和 src
之外,因此无需将其排除在自动服务加载之外。
我正在将 Symfony 集成到一个旧的应用程序中,该应用程序具有自己的基于 PSR-11 的依赖项容器。一直在寻找将 DI 容器合并到 Symfony 使用的解决方案,但一无所获。为了让它发挥作用,我提出了一个我不喜欢的“hacky”解决方案。
我创建了这个 class。它在其中创建了一个旧 DI 容器的实例:
class OldAppServiceFactory
{
private ContainerInterface $container;
public function __construct()
{
$this->container = OldContainerFactory::create();
}
public function factory(string $className)
{
return $this->container->get($className);
}
}
并向 services.yaml
添加了正确的条目:
oldapp.service_factory:
class: Next\Service\LeonContainer\LeonServiceFactory
OldApp\Repository\Repository1:
factory: ['@oldapp.service_factory', 'factory']
arguments:
- 'OldApp\Repository\Repository1'
OldApp\Repository\Repository2:
factory: ['@oldapp.service_factory', 'factory']
arguments:
- 'OldApp\Repository\Repository2'
OldApp\configuration\ConfigurationProviderInterface:
factory: ['@oldapp.service_factory', 'factory']
arguments:
- 'OldApp\configuration\ConfigurationProviderInterface'
通过上述 hack,将那些 classes 投入服务 class 构造函数工作。不幸的是,它看起来很糟糕,并且用更多这样的存储库扩展它会很痛苦(尤其是当有 50 个时)。是否有可能在 services.yaml
中实现这样的目标?
OldApp\Repository\:
factory: ['@oldapp.service_factory', 'factory']
arguments:
- << PASS FQCN HERE >>
这将使我在 services.yaml
中只有一个条目用于旧应用程序的单个命名空间。
但是,也许我的问题还有其他解决方案?一直在尝试配置 Kernel.php
和 prepareContainer(...)
方法,但我也一无所获,因为旧的依赖项位于一个返回数组的 PHP 文件中:
return array [
RepositoryMetadataCache::class => static fn () => RepositoryMetadataCache::createFromCacheFile(),
EntityCollection::class => autowire(EntityCollection::class),
'Model\Repository\*' => static function (ContainerInterface $container, RequestedEntry $entry) { ... }
];
您可以使用自定义编译器传递轻松完成此操作。
首先通过加载它们存在的目录来标记所有旧存储库类:
OldApp\Repository\:
resource: '../src/OldApp/Repository/*'
autowire: false
autoconfigure: false
tags: ['oldapp_repository']
(我认为您可能还需要从默认的自动服务加载中排除 src/OldApp
。例如:
App\:
resource: '../src/*'
exclude: '../src/{OldApp/Repository,DependencyInjection,Entity,Tests,Kernel.php}'
...但我不是 100% 确定,测试这个)。
然后创建一个编译器通道来遍历标签并为每个标签定义一个工厂:
class OldAppRepositoryCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$taggedServices = $container->findTaggedServiceIds('oldapp_repository');
foreach ($taggedServices as $serviceId => $tags) {
$definition = $container->getDefinition($serviceId);
$definition
->setFactory([new Reference('oldapp.service_factory'), 'factory'])
->addArgument($serviceId);
}
}
}
并在您的应用程序内核 build()
方法中添加编译器传递:
// src/Kernel.php
namespace App;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
// ...
class Kernel extends BaseKernel
{
// ...
protected function build(ContainerBuilder $container): void
{
$container->addCompilerPass(new OldAppRepositoryCompilerPass());
}
}
目前无法对此进行正确测试,但这应该会让您朝着正确的方向前进。有关其他详细信息,请查看文档:
您可以检查 this example repo 以上内容的实施和工作地点。在此 repo 中,OldApp
命名空间在 App
和 src
之外,因此无需将其排除在自动服务加载之外。