通过 class name from iterable - injected tagged services 获取服务
Get service via class name from iterable - injected tagged services
我正在努力通过 class 名称从一组注入的标记服务中获取特定服务。
这是一个例子:
我将所有实现 DriverInterface
的服务标记为 app.driver
并将其绑定到 $drivers
变量。
在其他一些服务中,我需要获取所有标记为 app.driver
的驱动程序并实例化并仅使用其中的少数。但是需要的驱动程序是动态的。
services.yml
_defaults:
autowire: true
autoconfigure: true
public: false
bind:
$drivers: [!tagged app.driver]
_instanceof:
DriverInterface:
tags: ['app.driver']
一些其他服务:
/**
* @var iterable
*/
private $drivers;
/**
* @param iterable $drivers
*/
public function __construct(iterable $drivers)
{
$this->drivers = $drivers;
}
public function getDriverByClassName(string $className): DriverInterface
{
????????
}
因此实现 DriverInterface
的服务作为可迭代结果被注入到 $this->drivers
参数中。我只能foreach
通过它们,但是所有服务都会被实例化。
是否有其他方法可以通过 class 名称注入这些服务以获取特定服务而不实例化其他服务?
我知道有可能制作这些驱动程序 public 并改用容器,但如果可能的话,我想避免将容器注入服务。
A ServiceLocator 将允许通过名称访问服务而无需实例化其余服务。它确实需要编译器通过,但设置起来并不难。
use Symfony\Component\DependencyInjection\ServiceLocator;
class DriverLocator extends ServiceLocator
{
// Leave empty
}
# Some Service
public function __construct(DriverLocator $driverLocator)
{
$this->driverLocator = $driverLocator;
}
public function getDriverByClassName(string $className): DriverInterface
{
return $this->driverLocator->get($fullyQualifiedClassName);
}
魔法来了:
# src/Kernel.php
# Make your kernel a compiler pass
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
class Kernel extends BaseKernel implements CompilerPassInterface {
...
# Dynamically add all drivers to the locator using a compiler pass
public function process(ContainerBuilder $container)
{
$driverIds = [];
foreach ($container->findTaggedServiceIds('app.driver') as $id => $tags) {
$driverIds[$id] = new Reference($id);
}
$driverLocator = $container->getDefinition(DriverLocator::class);
$driverLocator->setArguments([$driverIds]);
}
很快。假设您修复了我可能引入的任何语法错误或拼写错误,它应该可以工作。
为了获得额外的奖励,您可以自动注册您的驱动程序 类 并在您的服务文件中删除该 instanceof 条目。
# Kernel.php
protected function build(ContainerBuilder $container)
{
$container->registerForAutoconfiguration(DriverInterface::class)
->addTag('app.driver');
}
您不再(自 Symfony 4 起)需要创建编译器传递来配置服务定位器。
可以通过配置完成所有事情,让 Symfony 执行“魔法”。
您可以在配置中添加以下内容:
services:
_instanceof:
DriverInterface:
tags: ['app.driver']
lazy: true
DriverConsumer:
arguments:
- !tagged_locator
tag: 'app.driver'
需要访问这些而不是接收 iterable
的服务接收 ServiceLocatorInterface
:
class DriverConsumer
{
private $drivers;
public function __construct(ServiceLocatorInterface $locator)
{
$this->locator = $locator;
}
public function foo() {
$driver = $this->locator->get(Driver::class);
// where Driver is a concrete implementation of DriverInterface
}
}
就是这样。您不需要其他任何东西,它就可以工作tm.
完整示例
包含所有 classes 的完整示例。
我们有:
FooInterface
:
interface FooInterface
{
public function whoAmI(): string;
}
AbstractFoo
为了简化实施,我们将在具体服务中扩展一个抽象 class:
abstract class AbstractFoo implements FooInterface
{
public function whoAmI(): string {
return get_class($this);
}
}
服务实施
几个实现 FooInterface
的服务
class FooOneService extends AbstractFoo { }
class FooTwoService extends AbstractFoo { }
服务的消费者
另一个服务需要服务定位器才能使用我们刚刚定义的这两个服务:
class Bar
{
/**
* @var \Symfony\Component\DependencyInjection\ServiceLocator
*/
private $service_locator;
public function __construct(ServiceLocator $service_locator) {
$this->service_locator = $service_locator;
}
public function handle(): string {
/** @var \App\Test\FooInterface $service */
$service = $this->service_locator->get(FooOneService::class);
return $service->whoAmI();
}
}
配置
唯一需要的配置是:
services:
_instanceof:
App\Test\FooInterface:
tags: ['test_foo_tag']
lazy: true
App\Test\Bar:
arguments:
- !tagged_locator
tag: 'test_foo_tag'
服务名称的 FQCN 替代方法
如果您不想使用 class 名称来定义自己的服务名称,则可以使用静态方法来定义服务名称。配置将更改为:
App\Test\Bar:
arguments:
- !tagged_locator
tag: 'test_foo_tag'
default_index_method: 'fooIndex'
其中 fooIndex
是在 returns 字符串的每个服务上定义的 public 静态方法。注意:如果您使用此方法,您将无法通过他们的 class 名称获得服务。
我正在努力通过 class 名称从一组注入的标记服务中获取特定服务。
这是一个例子:
我将所有实现 DriverInterface
的服务标记为 app.driver
并将其绑定到 $drivers
变量。
在其他一些服务中,我需要获取所有标记为 app.driver
的驱动程序并实例化并仅使用其中的少数。但是需要的驱动程序是动态的。
services.yml
_defaults:
autowire: true
autoconfigure: true
public: false
bind:
$drivers: [!tagged app.driver]
_instanceof:
DriverInterface:
tags: ['app.driver']
一些其他服务:
/**
* @var iterable
*/
private $drivers;
/**
* @param iterable $drivers
*/
public function __construct(iterable $drivers)
{
$this->drivers = $drivers;
}
public function getDriverByClassName(string $className): DriverInterface
{
????????
}
因此实现 DriverInterface
的服务作为可迭代结果被注入到 $this->drivers
参数中。我只能foreach
通过它们,但是所有服务都会被实例化。
是否有其他方法可以通过 class 名称注入这些服务以获取特定服务而不实例化其他服务?
我知道有可能制作这些驱动程序 public 并改用容器,但如果可能的话,我想避免将容器注入服务。
A ServiceLocator 将允许通过名称访问服务而无需实例化其余服务。它确实需要编译器通过,但设置起来并不难。
use Symfony\Component\DependencyInjection\ServiceLocator;
class DriverLocator extends ServiceLocator
{
// Leave empty
}
# Some Service
public function __construct(DriverLocator $driverLocator)
{
$this->driverLocator = $driverLocator;
}
public function getDriverByClassName(string $className): DriverInterface
{
return $this->driverLocator->get($fullyQualifiedClassName);
}
魔法来了:
# src/Kernel.php
# Make your kernel a compiler pass
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
class Kernel extends BaseKernel implements CompilerPassInterface {
...
# Dynamically add all drivers to the locator using a compiler pass
public function process(ContainerBuilder $container)
{
$driverIds = [];
foreach ($container->findTaggedServiceIds('app.driver') as $id => $tags) {
$driverIds[$id] = new Reference($id);
}
$driverLocator = $container->getDefinition(DriverLocator::class);
$driverLocator->setArguments([$driverIds]);
}
很快。假设您修复了我可能引入的任何语法错误或拼写错误,它应该可以工作。
为了获得额外的奖励,您可以自动注册您的驱动程序 类 并在您的服务文件中删除该 instanceof 条目。
# Kernel.php
protected function build(ContainerBuilder $container)
{
$container->registerForAutoconfiguration(DriverInterface::class)
->addTag('app.driver');
}
您不再(自 Symfony 4 起)需要创建编译器传递来配置服务定位器。
可以通过配置完成所有事情,让 Symfony 执行“魔法”。
您可以在配置中添加以下内容:
services:
_instanceof:
DriverInterface:
tags: ['app.driver']
lazy: true
DriverConsumer:
arguments:
- !tagged_locator
tag: 'app.driver'
需要访问这些而不是接收 iterable
的服务接收 ServiceLocatorInterface
:
class DriverConsumer
{
private $drivers;
public function __construct(ServiceLocatorInterface $locator)
{
$this->locator = $locator;
}
public function foo() {
$driver = $this->locator->get(Driver::class);
// where Driver is a concrete implementation of DriverInterface
}
}
就是这样。您不需要其他任何东西,它就可以工作tm.
完整示例
包含所有 classes 的完整示例。
我们有:
FooInterface
:
interface FooInterface
{
public function whoAmI(): string;
}
AbstractFoo
为了简化实施,我们将在具体服务中扩展一个抽象 class:
abstract class AbstractFoo implements FooInterface
{
public function whoAmI(): string {
return get_class($this);
}
}
服务实施
几个实现 FooInterface
class FooOneService extends AbstractFoo { }
class FooTwoService extends AbstractFoo { }
服务的消费者
另一个服务需要服务定位器才能使用我们刚刚定义的这两个服务:
class Bar
{
/**
* @var \Symfony\Component\DependencyInjection\ServiceLocator
*/
private $service_locator;
public function __construct(ServiceLocator $service_locator) {
$this->service_locator = $service_locator;
}
public function handle(): string {
/** @var \App\Test\FooInterface $service */
$service = $this->service_locator->get(FooOneService::class);
return $service->whoAmI();
}
}
配置
唯一需要的配置是:
services:
_instanceof:
App\Test\FooInterface:
tags: ['test_foo_tag']
lazy: true
App\Test\Bar:
arguments:
- !tagged_locator
tag: 'test_foo_tag'
服务名称的 FQCN 替代方法
如果您不想使用 class 名称来定义自己的服务名称,则可以使用静态方法来定义服务名称。配置将更改为:
App\Test\Bar:
arguments:
- !tagged_locator
tag: 'test_foo_tag'
default_index_method: 'fooIndex'
其中 fooIndex
是在 returns 字符串的每个服务上定义的 public 静态方法。注意:如果您使用此方法,您将无法通过他们的 class 名称获得服务。