Symfony 自动装配独白通道

Posted

技术标签:

【中文标题】Symfony 自动装配独白通道【英文标题】:Symfony autowiring monolog channels 【发布时间】:2017-10-02 22:36:29 【问题描述】:

按照这个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 ? 

【问题讨论】:

之前的食谱条目解释说:Using a logger inside a service,请参阅monolog.logger 示例。 据我所知,你目前不能这样做 :( (Symfony 3.3)。在 Setter 上有一个 DI 会很好,它的参数可以是现有的已定义服务,例如:例如,通过注释“@monolog.logger.custom_channel”。我现在做的是为记录器创建一个自定义类,注入“@monolog.logger.custom_channel”,然后在我想使用的类中使用自动装配记录器,因此如果 DI Setter 功能在未来出现,将进行调整,但自动装配将保留在主类中。 【参考方案1】:

我写了(也许更复杂的)方法。我不想标记我的自动连接服务来告诉 symfony 使用哪个频道。 在 php 7.1 中使用 symfony 4。

我使用 monolog.channels 中定义的所有附加通道构建了 LoggerFactory。

我的工厂在 bundle 中,所以在 Bundle.php 添加

$container->addCompilerPass(
    new LoggerFactoryPass(), 
    PassConfig::TYPE_BEFORE_OPTIMIZATION, 
    1
); // -1 call before monolog

monolog.bundle 之前调用此编译器传递很重要,因为传递之后的独白会从容器中删除参数。

现在,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!');

【讨论】:

这是一个相当不错的编译器通道应用程序,干得好 :) 另一方面,我更喜欢我的服务/操作/控制器根本不知道可用通道。它将它们与特定的实现紧密结合。我非常喜欢只注入 LoggerInterface 类并使用配置文件计划通道/注入/等。您的方式将使测试更加困难,因为您将无法仅将虚拟记录器注入服务构造函数。您必须注入记录器工厂,并使用正确的通道创建此工厂并将通道名称存储在代码中。【参考方案2】:

经过一番搜索,我发现了一种使用标签并手动将几个参数注入自动装配服务的解决方法。

我的回答与@Thomas-Landauer 相似。不同之处在于,我不必手动创建记录器服务,因为 monolog 包中的编译器传递为我完成了这项工作。

services:
    _defaults:
        autowire: true
        autoconfigure: true
    AppBundle\Services\FooService:
        arguments:
            $loggerInterface: '@logger'
        tags:
            -  name: monolog.logger, channel: barchannel 

【讨论】:

【参考方案3】:

您可以使用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)

...

【讨论】:

但是,如果你在 _defaults 中绑定 LoggerInterface 服务,Symfony 期望在每个服务构造函数中找到参数!至少对我来说 Symfony 3.4。例如,如果我有一个没有定义 $loggerMyApi 参数的服务,Symfony 会抛出一个错误:Unused binding "$loggerMyApi" in service Foo【参考方案4】:

从 MonologBu​​ndle 3.5 开始,您可以自动装配不同的 Monolog 通过使用以下类型提示您的服务参数来获取频道 语法:Psr\Log\LoggerInterface $<channel>Logger。例如,要 注入与应用记录器通道相关的服务使用:

public function __construct(LoggerInterface $appLogger)

   $this->logger = $appLogger;

https://symfony.com/doc/current/logging/channels_handlers.html#monolog-autowire-channels

【讨论】:

试图让这个工作。使用 Symfony 5(monolog-bundle 3.5),但总是注入app 通道记录器,尽管有任何参数名称组合。 发现这种功能只适用于 Symfony 4.2+,因此参数中的通道应该在monolog.channels 配置数组中定义。这样它将使用为参数功能注册别名来编译容器。 对于文档中承诺的魔法,尽管有标签,但捆绑包中没有代码可以处理这个问题(因为如果在标签中没有指定频道,频道处理将被跳过)【参考方案5】:

我没有找到自动连接记录器通道的方法。但是,我找到了一种使用autowire原则上的方法,并且只手动注入记录器。使用您的class FooService,这就是services.yml 的样子(Symfony 3.3):

# services.yml

services:
    _defaults:
        autowire: true
        autoconfigure: true
    AppBundle\Services\FooService:
        arguments:
            $loggerInterface: '@monolog.logger.barchannel'

所以“诀窍”是显式注入记录器通道,同时仍然通过自动装配注入该服务的所有其他依赖项。

【讨论】:

【参考方案6】:

基本上,您有两个选择:

一、服务标签:

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']

你可以阅读更多here和here。

【讨论】:

不是我的反对意见,但是,虽然我想这是对频道的一个很好的简洁解释,但它并没有回答如何使用它们进行自动装配。 我的赞成票。这个答案是正确的,自动装配在这里不是问题。【参考方案7】:

最近我正在通过 MonologBu​​ndle 实现对所有注册记录器的单点访问。 而且我还尝试做一些更好的解决方案 - 并做了自动生成的记录器装饰器。每个类装饰一个已注册独白通道的一个对象。

链接到捆绑包adrenalinkin/monolog-autowire-bundle

【讨论】:

【参考方案8】:

对于那些仍在为此苦苦挣扎的人。 在 Symfony 4.3 中,除此之外,我还为特定通道添加了一个别名,因为没有它,它只能在开发环境中工作:构建时,单元测试都失败了,因为自定义记录器是一个未定义的服务.

 monolog.logger.my_custom_logger:         
   alias: Psr\Log\LoggerInterface         
   public: true 

 App\Logger\MyLogger:         
   arguments:             
     $logger: '@monolog.logger.my_custom_logger'

【讨论】:

【参考方案9】:

从documentation 开始,现在可以根据参数名称的类型提示进行自动装配。

// autowires monolog with "foo" channel
public function __construct(\Psr\Log\LoggerInterface $fooLogger);

【讨论】:

以上是关于Symfony 自动装配独白通道的主要内容,如果未能解决你的问题,请参考以下文章

无法自动装配服务 FOSUserBundle,Symfony 3.4

Symfony 4 不自动装配动态路由控制器

使用 Symfony 自动装配时是不是可以排除多个目录?

无法自动装配服务:参数引用类但不存在此类服务

Hwi oauth bundle 和 Symfony 3.4 无法自动装配服务:如何在 symfony 3.4 + FOSUserBundle 中使用 hwi/oauth-bundle

SpringBoot的自动装配(一)