Symfony で簡単にログを記録する方法は?


こんにちは coderz、調子はどうですか?あなたがブログ投稿にうんざりしていて、たくさんの仕事があることは知っていますが、これをチェックしてください!



目標:


  • プロジェクト内のすべてのクラスを「ロギング可能」にしますが、可能な限り最小限の変更とボイラープレートの冗長性を備えています.

  • 古典的な方法:


  • したがって、モノログのロガー (どのように構成しても) が最後にタグ付けされたサービスであることを知っているので、それを「クリーンに」使用する「唯一の」方法は、必要なサービスに DI/注入することです

  • Symfony でのサービスの注入は、複数のアプローチと戦略で行うことができます.しかし、最良のケースでは、少なくとも次の 2 つのことに触れる必要があります.
  • プライベート プロパティを定義してロガー サービス インスタンスを受け取り、実際にサービスを受け取るためにコンストラクターで代入を定義することにより、ロガーを必要とするクラス.
  • コンストラクター インジェクションではなくセッター インジェクションを選択した場合の services.yaml (後者はオートワイヤリングの恩恵を受けることができるため)

  • 現在、ロギングは「便利で必要な」機能であるため、あちこちでログを記録したいという衝動が何度も高まる可能性があり、さらに 1 行のプロパティとコンストラクター内の別の行でコードを汚染するのはばかげていると感じるかもしれません.場合によっては、ロガーの DI インジェクションのためにコンストラクターを作成するだけです.この正確なスニペットを多数のクラスにコピー/貼り付けすると、本当に不満を感じるかもしれません.

  • ショートカット:



  • PHPStorm のオートコンプリート機能を使用すると、同じプレフィックスを持つインターフェイス/トレイトのペアに気付くでしょう.
  • Psr\Log\LoggerAwareInterface
  • Psr\Log\LoggerAwareTrait

  • 上記の問題を解決するためにそれらをまとめて使用できますか? →はい.
  • 一般に、SomethingAwareInterface 命名パターンは、クラスが「setSomething()」という名前のメソッドを持つことになっていることを意味します.
  • また、慣例に従って、セッターを持つということは、セッターが変更する「何か」プロパティがクラスに必要であることを意味し、そのプロパティを定義する SomethingAwareTrait がここに来ます.
  • そのインターフェイス/特性を実装/使用すると、クラスに「something」という名前のプロパティとそのセッターが作成されます.ここで良いことは、コードの冗長性が裏庭に完全に隠されていることです.
  • まだ 1 つの障害があります.それは、特性が実際にサービス インスタンスを取得する方法です.
  • 考えられる解決策の 1 つは、setLogger() メソッドの上の「@required」アノテーションですが、この場合、setter は使用されたトレイトで定義されており、それを変更することはできません.


  • 1 回限りの解決策は、アプリケーションのカーネルをわずかに変更して、コンテナが構築される前にすべてのサービスをループし、サービスが「somethingAware」であるかどうかを確認してから、明示的にサービス.
    上記のペアを実装/使用するプロジェクトの任意のクラスを作成し、追加のオーバーヘッドなしですぐにロガーを使用できるようにするためのショーケースを次に示します.

  • <?php
    
    // src/Kernel.php
    
    namespace App;
    
    use DateTime;
    use Psr\Log\LoggerAwareInterface;
    use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
    use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
    use Symfony\Component\DependencyInjection\ContainerBuilder;
    use Symfony\Component\DependencyInjection\Definition;
    use Symfony\Component\HttpKernel\Kernel as BaseKernel;
    
    class Kernel extends BaseKernel implements CompilerPassInterface
    {
        use MicroKernelTrait;
    
        public function process(ContainerBuilder $container): void
        {
            $definitions = $container->getDefinitions();
            foreach ($definitions as $definition) {
                if (!$this->isAware($definition, LoggerAwareInterface::class)) {
                    continue;
                }
                $definition->addMethodCall(
                     'setLogger',
                    [$container->getDefinition('monolog.logger')]
                 );
            }
        }
    
     private function isAware(Definition $definition, string $awarenessClass): bool
     {
         $serviceClass = $definition->getClass();
         if ($serviceClass === null) {
             return false;
         }
         $implementedClasses = @class_implements($serviceClass, false);
         if (empty($implementedClasses)) {
             return false;
         }
         if (\array_key_exists($awarenessClass, $implementedClasses)) {
             return true;
         }
    
         return false;
     }
    
    }
    


  • たとえば、インターフェイスを実装し、コマンドで特性を使用するだけで、準備完了です!

  • <?php
    
    // src/Command/MyCommand.php
    
    declare(strict_types=1);
    
    namespace App\Command;
    
    use Psr\Log\LoggerAwareInterface;
    use Psr\Log\LoggerAwareTrait;
    use Symfony\Component\Console\Attribute\AsCommand;
    use Symfony\Component\Console\Command\Command;
    use Symfony\Component\Console\Input\InputInterface;
    use Symfony\Component\Console\Output\OutputInterface;
    
    #[AsCommand(
        name: 'app:my-command',
        description: 'test!',
    )]
    class MyCommand extends Command implements LoggerAwareInterface
    {
        use LoggerAwareTrait;
    
        protected function execute(InputInterface $input, OutputInterface $output): int
        {
            $this->logger->info('I can log!');
    
            return Command::SUCCESS;
        }
    }
    
    


  • この gist on github からも上記のコード スニペットを複製/ダウンロードできます

  • 今日の話はこれで十分です.お役に立てば幸いです.