diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php new file mode 100644 index 0000000000..262017f2a4 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php @@ -0,0 +1,86 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Find all service tags which are defined, but not used and yield a warning log message. + * + * @author Florian Pfitzer + */ +class UnusedTagsPass implements CompilerPassInterface +{ + /** + * whitelisted tags + * + * @var array + */ + protected $whitelist = array( + "console.command", + "data_collector", + "form.type", + "form.type_extension", + "form.type_guesser", + "kernel.cache_clearer", + "kernel.cache_warmer", + "kernel.event_listener", + "kernel.event_subscriber", + "kernel.fragment_renderer", + "monolog.logger", + "routing.loader", + "security.remember_me_aware", + "security.voter", + "serializer.encoder", + "templating.helper", + "translation.dumper", + "translation.extractor", + "translation.loader", + "twig.extension", + "twig.loader", + "validator.constraint_validator", + "validator.initializer", + ); + + public function process(ContainerBuilder $container) + { + $compiler = $container->getCompiler(); + $formatter = $compiler->getLoggingFormatter(); + $tags = $container->findTags(); + + $unusedTags = $container->findUnusedTags(); + foreach ($unusedTags as $tag) { + // skip whitelisted tags + if (in_array($tag, $this->whitelist)) { + continue; + } + // check for typos + $candidates = array(); + foreach ($tags as $definedTag) { + if ($definedTag === $tag) { + continue; + } + if (false !== strpos($definedTag, $tag) || levenshtein($tag, $definedTag) <= strlen($tag) / 3) { + $candidates[] = $definedTag; + } + } + + $services = array_keys($container->findTaggedServiceIds($tag)); + $message = sprintf('Tag "%s" was defined on the service(s) %s, but was never used.', $tag, implode(',', $services)); + if (!empty($candidates)) { + $message .= sprintf(' Did you mean "%s"?', implode('", "', $candidates)); + } + $compiler->addLogMessage($formatter->format($this, $message)); + } + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 7b1c77f225..1cdf83df5d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -28,6 +28,7 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CompilerDebugDum use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationExtractorPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslationDumperPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SerializerPass; +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\UnusedTagsPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ConfigCachePass; use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -89,6 +90,7 @@ class FrameworkBundle extends Bundle $container->addCompilerPass(new TranslationDumperPass()); $container->addCompilerPass(new FragmentRendererPass(), PassConfig::TYPE_AFTER_REMOVING); $container->addCompilerPass(new SerializerPass()); + $container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING); if ($container->getParameter('kernel.debug')) { $container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_AFTER_REMOVING); diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 52405413af..ca8a136d25 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -90,6 +90,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface */ private $expressionLanguageProviders = array(); + /** + * @var array with tag names used by findTaggedServiceIds + */ + private $usedTags = array(); + /** * Sets the track resources flag. * @@ -1064,6 +1069,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface */ public function findTaggedServiceIds($name) { + $this->usedTags[] = $name; $tags = array(); foreach ($this->getDefinitions() as $id => $definition) { if ($definition->hasTag($name)) { @@ -1089,6 +1095,19 @@ class ContainerBuilder extends Container implements TaggedContainerInterface return array_unique($tags); } + /** + * Returns all tags not queried by findTaggedServiceIds + * + * @return array An array of tags + */ + public function findUnusedTags() + { + $tags = array_values(array_diff($this->findTags(), $this->usedTags)); + $tags = array_unique($tags); + + return $tags; + } + public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) { $this->expressionLanguageProviders[] = $provider; diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 511b29c939..047a7cf334 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -560,6 +560,18 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array(), $builder->findTaggedServiceIds('foobar'), '->findTaggedServiceIds() returns an empty array if there is annotated services'); } + public function testFindUnusedTags() + { + $builder = new ContainerBuilder(); + $builder + ->register('foo', 'Bar\FooClass') + ->addTag('kernel.event_listener', array('foo' => 'foo')) + ->addTag('kenrel.event_listener', array('bar' => 'bar')) + ; + $builder->findTaggedServiceIds('kernel.event_listener'); + $this->assertEquals(array('kenrel.event_listener'), $builder->findUnusedTags(), '->findUnusedTags() returns an array with unused tags'); + } + /** * @covers Symfony\Component\DependencyInjection\ContainerBuilder::findDefinition */