diff --git a/DependencyInjection/Compiler/AddProcessorsPass.php b/DependencyInjection/Compiler/AddProcessorsPass.php index cc99b67d..5c276a9d 100644 --- a/DependencyInjection/Compiler/AddProcessorsPass.php +++ b/DependencyInjection/Compiler/AddProcessorsPass.php @@ -11,10 +11,11 @@ namespace Symfony\Bundle\MonologBundle\DependencyInjection\Compiler; +use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Reference; /** * Registers processors in Monolog loggers or handlers. @@ -25,46 +26,62 @@ */ class AddProcessorsPass implements CompilerPassInterface { + use PriorityTaggedServiceTrait; + public function process(ContainerBuilder $container) { if (!$container->hasDefinition('monolog.logger')) { return; } + $indexedTags = []; + $i = 1; + foreach ($container->findTaggedServiceIds('monolog.processor') as $id => $tags) { - foreach ($tags as $tag) { - if (!empty($tag['channel']) && !empty($tag['handler'])) { - throw new \InvalidArgumentException(\sprintf('you cannot specify both the "handler" and "channel" attributes for the "monolog.processor" tag on service "%s"', $id)); - } + foreach ($tags as &$tag) { + $indexedTags[$tag['index'] = $i++] = $tag; + } + unset($tag); + $definition = $container->getDefinition($id); + $definition->setTags(array_merge($definition->getTags(), ['monolog.processor' => $tags])); + } - if (!empty($tag['handler'])) { - $definition = $container->findDefinition(\sprintf('monolog.handler.%s', $tag['handler'])); - $parentDef = $definition; - while (!$parentDef->getClass() && $parentDef instanceof ChildDefinition) { - $parentDef = $container->findDefinition($parentDef->getParent()); - } - $class = $container->getParameterBag()->resolveValue($parentDef->getClass()); - if (!method_exists($class, 'pushProcessor')) { - throw new \InvalidArgumentException(\sprintf('The "%s" handler does not accept processors', $tag['handler'])); - } - } elseif (!empty($tag['channel'])) { - if ('app' === $tag['channel']) { - $definition = $container->getDefinition('monolog.logger'); - } else { - $definition = $container->getDefinition(\sprintf('monolog.logger.%s', $tag['channel'])); - } - } else { - $definition = $container->getDefinition('monolog.logger_prototype'); - } + $taggedIteratorArgument = new TaggedIteratorArgument('monolog.processor', 'index', null, true); + // array_reverse is used because ProcessableHandlerTrait::pushProcessor prepends processors to the beginning of the stack + foreach (array_reverse($this->findAndSortTaggedServices($taggedIteratorArgument, $container), true) as $index => $reference) { + $tag = $indexedTags[$index]; - if (!empty($tag['method'])) { - $processor = [new Reference($id), $tag['method']]; + if (!empty($tag['channel']) && !empty($tag['handler'])) { + throw new \InvalidArgumentException(\sprintf('you cannot specify both the "handler" and "channel" attributes for the "monolog.processor" tag on service "%s"', $reference)); + } + + if (!empty($tag['handler'])) { + $definition = $container->findDefinition(\sprintf('monolog.handler.%s', $tag['handler'])); + $parentDef = $definition; + while (!$parentDef->getClass() && $parentDef instanceof ChildDefinition) { + $parentDef = $container->findDefinition($parentDef->getParent()); + } + $class = $container->getParameterBag()->resolveValue($parentDef->getClass()); + if (!method_exists($class, 'pushProcessor')) { + throw new \InvalidArgumentException(\sprintf('The "%s" handler does not accept processors', $tag['handler'])); + } + } elseif (!empty($tag['channel'])) { + if ('app' === $tag['channel']) { + $definition = $container->getDefinition('monolog.logger'); } else { - // If no method is defined, fallback to use __invoke - $processor = new Reference($id); + $definition = $container->getDefinition(\sprintf('monolog.logger.%s', $tag['channel'])); } - $definition->addMethodCall('pushProcessor', [$processor]); + } else { + $definition = $container->getDefinition('monolog.logger_prototype'); + } + + if (!empty($tag['method'])) { + $processor = [$reference, $tag['method']]; + } else { + // If no method is defined, fallback to use __invoke + $processor = $reference; } + $definition->addMethodCall('pushProcessor', [$processor]); } } } diff --git a/Tests/DependencyInjection/Compiler/AddProcessorsPassTest.php b/Tests/DependencyInjection/Compiler/AddProcessorsPassTest.php index c11c2a33..48383552 100644 --- a/Tests/DependencyInjection/Compiler/AddProcessorsPassTest.php +++ b/Tests/DependencyInjection/Compiler/AddProcessorsPassTest.php @@ -21,6 +21,7 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; class AddProcessorsPassTest extends TestCase { @@ -31,12 +32,28 @@ public function testHandlerProcessors() $service = $container->getDefinition('monolog.handler.test'); $calls = $service->getMethodCalls(); $this->assertCount(1, $calls); - $this->assertEquals(['pushProcessor', [new Reference('test')]], $calls[0]); + $this->assertEquals(['pushProcessor', [new TypedReference('test', 'TestClass')]], $calls[0]); $service = $container->getDefinition('handler_test'); $calls = $service->getMethodCalls(); $this->assertCount(1, $calls); - $this->assertEquals(['pushProcessor', [new Reference('test2')]], $calls[0]); + $this->assertEquals(['pushProcessor', [new TypedReference('test2', 'TestClass')]], $calls[0]); + + $service = $container->getDefinition('monolog.handler.priority_test'); + $calls = $service->getMethodCalls(); + $this->assertCount(5, $calls); + $this->assertEquals(['pushProcessor', [new TypedReference('processor-10', 'TestClass')]], $calls[0]); + $this->assertEquals(['pushProcessor', [new TypedReference('processor+10', 'TestClass')]], $calls[1]); + $this->assertEquals(['pushProcessor', [new TypedReference('processor+20', 'TestClass')]], $calls[2]); + $this->assertEquals(['pushProcessor', [new TypedReference('processor+20', 'TestClass')]], $calls[2]); + $this->assertEquals(['pushProcessor', [new TypedReference('processor+25+35', 'TestClass')]], $calls[3]); + $this->assertEquals(['pushProcessor', [new TypedReference('processor+35+25', 'TestClass')]], $calls[4]); + + $service = $container->getDefinition('monolog.handler.priority_test_2'); + $calls = $service->getMethodCalls(); + $this->assertCount(2, $calls); + $this->assertEquals(['pushProcessor', [new TypedReference('processor+35+25', 'TestClass')]], $calls[0]); + $this->assertEquals(['pushProcessor', [new TypedReference('processor+25+35', 'TestClass')]], $calls[1]); } public function testFailureOnHandlerWithoutPushProcessor() @@ -75,9 +92,13 @@ protected function getContainer() $container->setParameter('monolog.handler.console.class', ConsoleHandler::class); $container->setDefinition('monolog.handler.test', new Definition('%monolog.handler.console.class%', [100, false])); $container->setDefinition('handler_test', new Definition('%monolog.handler.console.class%', [100, false])); + $container->setDefinition('monolog.handler.priority_test', new Definition('%monolog.handler.console.class%', [100, false])); + $container->setDefinition('monolog.handler.priority_test_2', new Definition('%monolog.handler.console.class%', [100, false])); $container->setAlias('monolog.handler.test2', 'handler_test'); $definition->addMethodCall('pushHandler', [new Reference('monolog.handler.test')]); $definition->addMethodCall('pushHandler', [new Reference('monolog.handler.test2')]); + $definition->addMethodCall('pushHandler', [new Reference('monolog.handler.priority_test')]); + $definition->addMethodCall('pushHandler', [new Reference('monolog.handler.priority_test_2')]); $service = new Definition('TestClass', ['false', new Reference('logger')]); $service->addTag('monolog.processor', ['handler' => 'test']); @@ -87,6 +108,28 @@ protected function getContainer() $service->addTag('monolog.processor', ['handler' => 'test2']); $container->setDefinition('test2', $service); + $service = new Definition('TestClass', ['false', new Reference('logger')]); + $service->addTag('monolog.processor', ['handler' => 'priority_test', 'priority' => 10]); + $container->setDefinition('processor+10', $service); + + $service = new Definition('TestClass', ['false', new Reference('logger')]); + $service->addTag('monolog.processor', ['handler' => 'priority_test', 'priority' => -10]); + $container->setDefinition('processor-10', $service); + + $service = new Definition('TestClass', ['false', new Reference('logger')]); + $service->addTag('monolog.processor', ['handler' => 'priority_test', 'priority' => 20]); + $container->setDefinition('processor+20', $service); + + $service = new Definition('TestClass', ['false', new Reference('logger')]); + $service->addTag('monolog.processor', ['handler' => 'priority_test', 'priority' => 35]); + $service->addTag('monolog.processor', ['handler' => 'priority_test_2', 'priority' => 25]); + $container->setDefinition('processor+35+25', $service); + + $service = new Definition('TestClass', ['false', new Reference('logger')]); + $service->addTag('monolog.processor', ['handler' => 'priority_test', 'priority' => 25]); + $service->addTag('monolog.processor', ['handler' => 'priority_test_2', 'priority' => 35]); + $container->setDefinition('processor+25+35', $service); + $container->getCompilerPassConfig()->setOptimizationPasses([]); $container->getCompilerPassConfig()->setRemovingPasses([]); $container->addCompilerPass(new AddProcessorsPass());