Skip to content

Commit

Permalink
[FrameworkBundle] [DependencyInjection] added logging of unused tags …
Browse files Browse the repository at this point in the history
…during container compilation
  • Loading branch information
Florian Pfitzer authored and fabpot committed Sep 28, 2015
1 parent 156368f commit f51fe4a
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 0 deletions.
@@ -0,0 +1,86 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* 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 <pfitzer@wurzel3.de>
*/
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));
}
}
}
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
Expand Up @@ -28,6 +28,7 @@
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;
Expand Down Expand Up @@ -89,6 +90,7 @@ public function build(ContainerBuilder $container)
$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);
Expand Down
19 changes: 19 additions & 0 deletions src/Symfony/Component/DependencyInjection/ContainerBuilder.php
Expand Up @@ -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.
*
Expand Down Expand Up @@ -1064,6 +1069,7 @@ public function resolveServices($value)
*/
public function findTaggedServiceIds($name)
{
$this->usedTags[] = $name;
$tags = array();
foreach ($this->getDefinitions() as $id => $definition) {
if ($definition->hasTag($name)) {
Expand All @@ -1089,6 +1095,19 @@ public function findTags()
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;
Expand Down
Expand Up @@ -560,6 +560,18 @@ public function testfindTaggedServiceIds()
$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
*/
Expand Down

0 comments on commit f51fe4a

Please sign in to comment.