Symfony 自动装配独白通道
Symfony autowiring monolog channels
按照此 documentation,我可以创建许多频道,这些频道将创建具有以下名称的服务 monolog.logger.<channel_name>
如何使用 DI 注入和自动装配将这些服务注入到我的服务中?
class FooService
{
public function __construct(LoggerInterface $loggerInterface) { }
}
Yaml
#existing
foo_service:
class: AppBundle\Services\FooService
arguments: ["@monolog.logger.barchannel"]
# what I want to do
foo_service:
autowire: true # how to inject @monolog.logger.barchannel ?
我没有找到自动连接记录器通道的方法。但是,我找到了一种原则上 autowire
并仅手动注入记录器的方法。使用您的 class FooService
,这就是 services.yml
的样子(Symfony 3.3):
# services.yml
services:
_defaults:
autowire: true
autoconfigure: true
AppBundle\Services\FooService:
arguments:
$loggerInterface: '@monolog.logger.barchannel'
所以 "trick" 是显式注入记录器通道,同时仍然通过自动装配注入该服务的所有其他依赖项。
经过一番搜索,我发现了一些使用标签并手动将几个参数注入自动装配服务的解决方法。
我的回答看起来与@Thomas-Landauer 相似。不同之处在于,我不必手动创建记录器服务,因为来自 monolog bundle 的编译器会为我做这件事。
services:
_defaults:
autowire: true
autoconfigure: true
AppBundle\Services\FooService:
arguments:
$loggerInterface: '@logger'
tags:
- { name: monolog.logger, channel: barchannel }
我写了(可能更复杂)方法。我不想标记我的自动装配服务来告诉 symfony 使用哪个通道。
将 symfony 4 与 php 7.1.
一起使用
我用 monolog.channels.
中定义的所有附加通道构建了 LoggerFactory
我的工厂是捆绑的,所以在Bundle.php中添加
$container->addCompilerPass(
new LoggerFactoryPass(),
PassConfig::TYPE_BEFORE_OPTIMIZATION,
1
); // -1 call before monolog
在 monolog.bundle 之前调用这个编译器 pass 很重要,因为 monolog 在 pass 之后会从容器中删除参数。
现在,LoggerFactoryPass
namespace Bundle\DependencyInjection\Compiler;
use Bundle\Service\LoggerFactory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class LoggerFactoryPass implements CompilerPassInterface
{
/**
* You can modify the container here before it is dumped to PHP code.
* @param ContainerBuilder $container
* @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
*/
public function process(ContainerBuilder $container): void
{
if (!$container->has(LoggerFactory::class) || !$container->hasDefinition('monolog.logger')) {
return;
}
$definition = $container->findDefinition(LoggerFactory::class);
foreach ($container->getParameter('monolog.additional_channels') as $channel) {
$loggerId = sprintf('monolog.logger.%s', $channel);
$definition->addMethodCall('addChannel', [
$channel,
new Reference($loggerId)
]);
}
}
}
和 LoggerFactory
namespace Bundle\Service;
use Psr\Log\LoggerInterface;
class LoggerFactory
{
protected $channels = [];
public function addChannel($name, $loggerObject): void
{
$this->channels[$name] = $loggerObject;
}
/**
* @param string $channel
* @return LoggerInterface
* @throws \InvalidArgumentException
*/
public function getLogger(string $channel): LoggerInterface
{
if (!array_key_exists($channel, $this->channels)) {
throw new \InvalidArgumentException('You are trying to reach not defined logger channel');
}
return $this->channels[$channel];
}
}
所以,现在您可以注入 LoggerFactory,并选择您的频道
public function acmeAction(LoggerFactory $factory)
{
$logger = $factory->getLogger('my_channel');
$logger->log('this is awesome!');
}
您可以使用 bind parameter:
services:
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: true
bind:
$loggerMyApi: '@monolog.logger.my_api'
然后你可以在你服务的构造函数中使用它:
use Psr\Log\LoggerInterface;
...
public function __construct(LoggerInterface $loggerMyApi)
{
...
}
基本上,您有两个选择:
首先,服务标签:
services:
App\Log\FooLogger:
arguments: ['@logger']
tags:
- { name: monolog.logger, channel: foo }
然后您可以在其他地方使用您的 CustomLogger
作为依赖项
其次,您可以依靠 Monolog 为配置中的每个自定义通道自动注册记录器:
# config/packages/prod/monolog.yaml
monolog:
channels: ['foo', 'bar']
您将可以使用这些服务:monolog.logger.foo
、'monolog.logger.bar'
然后您可以从服务容器中检索它们,或手动连接它们,例如:
services:
App\Lib\MyService:
$fooLogger: ['@monolog.logger.foo']
最近我通过MonologBundle实现了对所有注册记录器的单点访问。
而且我还尝试做一些更好的解决方案 - 并做了自动生成的记录器装饰器。每个 class 装饰一个已注册独白频道的对象。
Link 到捆绑包 adrenalinkin/monolog-autowire-bundle
致那些仍在为此苦苦挣扎的人。
在 Symfony 4.3 中,最重要的是,我为特定通道添加了一个别名,因为没有它,它只能在开发环境中工作:构建时,单元测试都失败了,因为自定义记录器是一个未定义的服务.
monolog.logger.my_custom_logger:
alias: Psr\Log\LoggerInterface
public: true
App\Logger\MyLogger:
arguments:
$logger: '@monolog.logger.my_custom_logger'
Starting from MonologBundle 3.5 you can autowire different Monolog
channels by type-hinting your service arguments with the following
syntax: Psr\Log\LoggerInterface $<channel>Logger
. For example, to
inject the service related to the app logger channel use this:
public function __construct(LoggerInterface $appLogger)
{
$this->logger = $appLogger;
}
https://symfony.com/doc/current/logging/channels_handlers.html#monolog-autowire-channels
从 documentation 开始,现在可以根据参数名称的类型提示进行自动装配。
// autowires monolog with "foo" channel
public function __construct(\Psr\Log\LoggerInterface $fooLogger);
按照此 documentation,我可以创建许多频道,这些频道将创建具有以下名称的服务 monolog.logger.<channel_name>
如何使用 DI 注入和自动装配将这些服务注入到我的服务中?
class FooService
{
public function __construct(LoggerInterface $loggerInterface) { }
}
Yaml
#existing
foo_service:
class: AppBundle\Services\FooService
arguments: ["@monolog.logger.barchannel"]
# what I want to do
foo_service:
autowire: true # how to inject @monolog.logger.barchannel ?
我没有找到自动连接记录器通道的方法。但是,我找到了一种原则上 autowire
并仅手动注入记录器的方法。使用您的 class FooService
,这就是 services.yml
的样子(Symfony 3.3):
# services.yml
services:
_defaults:
autowire: true
autoconfigure: true
AppBundle\Services\FooService:
arguments:
$loggerInterface: '@monolog.logger.barchannel'
所以 "trick" 是显式注入记录器通道,同时仍然通过自动装配注入该服务的所有其他依赖项。
经过一番搜索,我发现了一些使用标签并手动将几个参数注入自动装配服务的解决方法。
我的回答看起来与@Thomas-Landauer 相似。不同之处在于,我不必手动创建记录器服务,因为来自 monolog bundle 的编译器会为我做这件事。
services:
_defaults:
autowire: true
autoconfigure: true
AppBundle\Services\FooService:
arguments:
$loggerInterface: '@logger'
tags:
- { name: monolog.logger, channel: barchannel }
我写了(可能更复杂)方法。我不想标记我的自动装配服务来告诉 symfony 使用哪个通道。 将 symfony 4 与 php 7.1.
一起使用我用 monolog.channels.
中定义的所有附加通道构建了 LoggerFactory我的工厂是捆绑的,所以在Bundle.php中添加
$container->addCompilerPass(
new LoggerFactoryPass(),
PassConfig::TYPE_BEFORE_OPTIMIZATION,
1
); // -1 call before monolog
在 monolog.bundle 之前调用这个编译器 pass 很重要,因为 monolog 在 pass 之后会从容器中删除参数。
现在,LoggerFactoryPass
namespace Bundle\DependencyInjection\Compiler;
use Bundle\Service\LoggerFactory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class LoggerFactoryPass implements CompilerPassInterface
{
/**
* You can modify the container here before it is dumped to PHP code.
* @param ContainerBuilder $container
* @throws \Symfony\Component\DependencyInjection\Exception\InvalidArgumentException
* @throws \Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException
*/
public function process(ContainerBuilder $container): void
{
if (!$container->has(LoggerFactory::class) || !$container->hasDefinition('monolog.logger')) {
return;
}
$definition = $container->findDefinition(LoggerFactory::class);
foreach ($container->getParameter('monolog.additional_channels') as $channel) {
$loggerId = sprintf('monolog.logger.%s', $channel);
$definition->addMethodCall('addChannel', [
$channel,
new Reference($loggerId)
]);
}
}
}
和 LoggerFactory
namespace Bundle\Service;
use Psr\Log\LoggerInterface;
class LoggerFactory
{
protected $channels = [];
public function addChannel($name, $loggerObject): void
{
$this->channels[$name] = $loggerObject;
}
/**
* @param string $channel
* @return LoggerInterface
* @throws \InvalidArgumentException
*/
public function getLogger(string $channel): LoggerInterface
{
if (!array_key_exists($channel, $this->channels)) {
throw new \InvalidArgumentException('You are trying to reach not defined logger channel');
}
return $this->channels[$channel];
}
}
所以,现在您可以注入 LoggerFactory,并选择您的频道
public function acmeAction(LoggerFactory $factory)
{
$logger = $factory->getLogger('my_channel');
$logger->log('this is awesome!');
}
您可以使用 bind parameter:
services:
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
public: true
bind:
$loggerMyApi: '@monolog.logger.my_api'
然后你可以在你服务的构造函数中使用它:
use Psr\Log\LoggerInterface;
...
public function __construct(LoggerInterface $loggerMyApi)
{
...
}
基本上,您有两个选择:
首先,服务标签:
services:
App\Log\FooLogger:
arguments: ['@logger']
tags:
- { name: monolog.logger, channel: foo }
然后您可以在其他地方使用您的 CustomLogger
作为依赖项
其次,您可以依靠 Monolog 为配置中的每个自定义通道自动注册记录器:
# config/packages/prod/monolog.yaml
monolog:
channels: ['foo', 'bar']
您将可以使用这些服务:monolog.logger.foo
、'monolog.logger.bar'
然后您可以从服务容器中检索它们,或手动连接它们,例如:
services:
App\Lib\MyService:
$fooLogger: ['@monolog.logger.foo']
最近我通过MonologBundle实现了对所有注册记录器的单点访问。 而且我还尝试做一些更好的解决方案 - 并做了自动生成的记录器装饰器。每个 class 装饰一个已注册独白频道的对象。
Link 到捆绑包 adrenalinkin/monolog-autowire-bundle
致那些仍在为此苦苦挣扎的人。 在 Symfony 4.3 中,最重要的是,我为特定通道添加了一个别名,因为没有它,它只能在开发环境中工作:构建时,单元测试都失败了,因为自定义记录器是一个未定义的服务.
monolog.logger.my_custom_logger:
alias: Psr\Log\LoggerInterface
public: true
App\Logger\MyLogger:
arguments:
$logger: '@monolog.logger.my_custom_logger'
Starting from MonologBundle 3.5 you can autowire different Monolog channels by type-hinting your service arguments with the following syntax:
Psr\Log\LoggerInterface $<channel>Logger
. For example, to inject the service related to the app logger channel use this:public function __construct(LoggerInterface $appLogger) { $this->logger = $appLogger; }
https://symfony.com/doc/current/logging/channels_handlers.html#monolog-autowire-channels
从 documentation 开始,现在可以根据参数名称的类型提示进行自动装配。
// autowires monolog with "foo" channel
public function __construct(\Psr\Log\LoggerInterface $fooLogger);