Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Drastically improved debug:autowiring + new autowiring info system
This introduces a new interface + tag that can be used by bundles to inform the core about the *purpose* of the autowirable classes/interfaces.

This is makes debug:autowiring human-focused: showing you a list of
the autowireable services based on the *purpose* of them.
  • Loading branch information
weaverryan committed Feb 11, 2018
1 parent 7f8e215 commit 6e10bf2
Show file tree
Hide file tree
Showing 17 changed files with 556 additions and 8 deletions.
Expand Up @@ -15,6 +15,8 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\DependencyInjection\Debug\AutowiringInfoManager;
use Symfony\Component\DependencyInjection\Debug\AutowiringTypeInfo;

/**
* A console command for autowiring information.
Expand All @@ -27,6 +29,15 @@ class DebugAutowiringCommand extends ContainerDebugCommand
{
protected static $defaultName = 'debug:autowiring';

private $autowiringInfoManager;

public function __construct(AutowiringInfoManager $autowiringInfoManager = null)
{
$this->autowiringInfoManager = $autowiringInfoManager;

parent::__construct();
}

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -78,24 +89,92 @@ protected function execute(InputInterface $input, OutputInterface $output)

asort($serviceIds);

$io->title('Autowirable Services');
$io->text('The following classes & interfaces can be used as type-hints when autowiring:');
if ($search) {
$io->text(sprintf('(only showing classes/interfaces matching <comment>%s</comment>)', $search));
}
$io->newLine();
$tableRows = array();

$keyServices = array();
$otherServices = array();
$hasAlias = array();
foreach ($serviceIds as $serviceId) {
if (null !== $this->autowiringInfoManager && $autowiringInfo = $this->autowiringInfoManager->getInfo($serviceId)) {
$keyServices[] = array(
'info' => $autowiringInfo,
'alias' => $builder->has($serviceId) ? $builder->getAlias($serviceId) : null,
);

continue;
}

if ($builder->hasAlias($serviceId)) {
$tableRows[] = array(sprintf('<fg=cyan>%s</fg=cyan>', $serviceId));
$tableRows[] = array(sprintf(' alias to %s', $builder->getAlias($serviceId)));
$hasAlias[(string) $builder->getAlias($serviceId)] = true;
} else {
$tableRows[$serviceId] = array(sprintf('<fg=cyan>%s</fg=cyan>', $serviceId));
$hasAlias[(string)$builder->getAlias($serviceId)] = true;
}

$otherServices[$serviceId] = array(
'type' => $serviceId,
'alias' => $builder->hasAlias($serviceId) ? $builder->getAlias($serviceId) : null,
);
}
$otherServices = array_diff_key($otherServices, $hasAlias);

usort($keyServices, function($a, $b) {
if ($a['info']->getPriority() === $b['info']->getPriority()) {
return 0;
}

return $a['info']->getPriority() > $b['info']->getPriority() ? -1 : 1;
});

$this->printKeyServices($keyServices, $io);

$io->table(array(), array_diff_key($tableRows, $hasAlias));
$this->printOtherServices($otherServices, $io);
}

private function printOtherServices(array $otherServices, SymfonyStyle $io)
{
if (empty($otherServices)) {
return;
}

// not necessary to print if this is the only list
if (null !== $this->autowiringInfoManager) {
$io->title('Other Services');
}

foreach ($otherServices as $serviceData) {
$io->writeln(sprintf('<fg=cyan>%s</fg=cyan>', $serviceData['type']));
if ($alias = $serviceData['alias']) {
$io->writeln(sprintf(' alias to %s', $alias));
}
}
}

private function printKeyServices(array $keyServices, SymfonyStyle $io)
{
if (empty($keyServices)) {
return;
}

$io->title('Key Services');
foreach ($keyServices as $serviceData) {
/** @var AutowiringTypeInfo $info */
$info = $serviceData['info'];

$nameLine = sprintf('<comment>%s</comment>', $info->getName());
if ($info->getDescription()) {
$nameLine .= sprintf(' (%s)', $info->getDescription());
}
$io->writeln($nameLine);

$io->writeln(sprintf(' Type: <fg=cyan>%s</fg=cyan>', $info->getType()));

if ($serviceData['alias']) {
$io->writeln(sprintf(' Alias to the %s service', $serviceData['alias']));
}

$io->writeln('');
}
}
}
@@ -0,0 +1,42 @@
<?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;
use Symfony\Component\DependencyInjection\Reference;

/**
* Looks for & processes debug.autowiring_info_provider tags.
*
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
class AutowireDebugInfoPass implements CompilerPassInterface
{
const AUTOWIRING_INFO_PROVIDER_TAG = 'debug.autowiring_info_provider';

public function process(ContainerBuilder $container)
{
if (false === $container->hasDefinition('debug.autowiring_info_manager')) {
return;
}

$definition = $container->getDefinition('debug.autowiring_info_manager');

$references = array();
foreach ($container->findTaggedServiceIds(self::AUTOWIRING_INFO_PROVIDER_TAG, true) as $serviceId => $attributes) {
$references[] = new Reference($serviceId);
}

$definition->replaceArgument(0, $references);
}
}
@@ -0,0 +1,105 @@
<?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;

use Doctrine\Common\Annotations\Reader;
use Psr\Cache\CacheItemPoolInterface;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\Asset\Packages;
use Symfony\Component\DependencyInjection\Debug\AutowiringInfoProviderInterface;
use Symfony\Component\DependencyInjection\Debug\AutowiringTypeInfo;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Form\FormFactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Stopwatch\Stopwatch;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Component\Workflow\Registry;

/**
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
final class FrameworkAutowiringInfoProvider implements AutowiringInfoProviderInterface
{
public function getTypeInfos(): array
{
return array(
AutowiringTypeInfo::create(CacheItemPoolInterface::class, 'Cache', 10)
->setDescription('general-purpose service for caching things'),

AutowiringTypeInfo::create(CacheInterface::class, 'Simple Cache', 10)
->setDescription('simpler cache, but less features'),

AutowiringTypeInfo::create(RouterInterface::class, 'Router', 10)
->setDescription('used to generate URLs'),

AutowiringTypeInfo::create(EventDispatcherInterface::class, 'Event Dispatcher')
->setDescription('used to dispatch custom events'),

AutowiringTypeInfo::create(Reader::class, 'Annotation Reader', -10),

AutowiringTypeInfo::create(ParameterBagInterface::class, 'Parameter Bag')
->setDescription('access service parameters'),

AutowiringTypeInfo::create(Filesystem::class, 'Filesystem')
->setDescription('helper for filesystem actions'),

AutowiringTypeInfo::create(RequestStack::class, 'Request Stack')
->setDescription('access the Request object'),

AutowiringTypeInfo::create(SessionInterface::class, 'Session'),

AutowiringTypeInfo::create(FlashBagInterface::class, 'Flash Bag')
->setDescription('use to set temporary success/failure messages'),

AutowiringTypeInfo::create(KernelInterface::class, 'Kernel'),

AutowiringTypeInfo::create(Stopwatch::class, 'Stopwatch')
->setDescription('use to add custom timings to profiler'),

AutowiringTypeInfo::create(Packages::class, 'Asset Packages')
->setDescription('use to generate URLs to assets'),

AutowiringTypeInfo::create(FormFactoryInterface::class, 'Form Factory')
->setDescription('use to create form objects'),

AutowiringTypeInfo::create(ValidatorInterface::class, 'Validator')
->setDescription('use to validate data against some constraints'),

AutowiringTypeInfo::create(TranslatorInterface::class, 'Translator'),

AutowiringTypeInfo::create(PropertyAccessorInterface::class, 'Property Accessor')
->setDescription('use to read dynamic keys from some data'),

AutowiringTypeInfo::create(CsrfTokenManagerInterface::class, 'CSRF Token Manager')
->setDescription('generate and check CSRF tokens'),

AutowiringTypeInfo::create(SerializerInterface::class, 'Serializer')
->setDescription('use to serialize data to JSON, XML, etc'),

AutowiringTypeInfo::create(Registry::class, 'Workflow')
->setDescription('use to fetch workflows'),

AutowiringTypeInfo::create(Registry::class, 'Workflow')
->setDescription('use to fetch workflows'),
);
}
}
Expand Up @@ -16,6 +16,7 @@
use Symfony\Bridge\Monolog\Processor\DebugProcessor;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AutowireDebugInfoPass;
use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader;
use Symfony\Bundle\FullStack;
use Symfony\Component\Cache\Adapter\AbstractAdapter;
Expand All @@ -33,6 +34,7 @@
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Debug\AutowiringInfoProviderInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
Expand Down Expand Up @@ -330,6 +332,8 @@ public function load(array $configs, ContainerBuilder $container)
->addTag('validator.constraint_validator');
$container->registerForAutoconfiguration(ObjectInitializerInterface::class)
->addTag('validator.initializer');
$container->registerForAutoconfiguration(AutowiringInfoProviderInterface::class)
->addTag(AutowireDebugInfoPass::AUTOWIRING_INFO_PROVIDER_TAG);

if (!$container->getParameter('kernel.debug')) {
// remove tagged iterator argument for resource checkers
Expand Down
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
Expand Up @@ -13,6 +13,7 @@

use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddAnnotationsCachedReaderPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddDebugLogProcessorPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AutowireDebugInfoPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CacheCollectorPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolPass;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\CachePoolClearerPass;
Expand Down Expand Up @@ -120,6 +121,7 @@ public function build(ContainerBuilder $container)
$container->addCompilerPass(new UnusedTagsPass(), PassConfig::TYPE_AFTER_REMOVING);
$container->addCompilerPass(new ContainerBuilderDebugDumpPass(), PassConfig::TYPE_BEFORE_REMOVING, -255);
$container->addCompilerPass(new CacheCollectorPass(), PassConfig::TYPE_BEFORE_REMOVING);
$container->addCompilerPass(new AutowireDebugInfoPass());
}
}

Expand Down
Expand Up @@ -56,6 +56,7 @@
</service>

<service id="console.command.debug_autowiring" class="Symfony\Bundle\FrameworkBundle\Command\DebugAutowiringCommand">
<argument type="service" id="debug.autowiring_info_manager" on-invalid="ignore" />
<tag name="console.command" command="debug:autowiring" />
</service>

Expand Down
8 changes: 8 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Resources/config/debug.xml
Expand Up @@ -23,5 +23,13 @@
<argument type="service" id="debug.argument_resolver.inner" />
<argument type="service" id="debug.stopwatch" />
</service>

<service id="debug.autowiring_info_manager" class="Symfony\Component\DependencyInjection\Debug\AutowiringInfoManager">
<argument /> <!-- argument info providers -->
</service>

<service id="debug.autowiring.framework_autowiring_info_provider" class="Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkAutowiringInfoProvider">
<tag name="debug.autowiring_info_provider" />
</service>
</services>
</container>
Expand Up @@ -31,6 +31,7 @@ public function testBasicFunctionality()

$this->assertContains('Symfony\Component\HttpKernel\HttpKernelInterface', $tester->getDisplay());
$this->assertContains('alias to http_kernel', $tester->getDisplay());
$this->assertContains('Simple Cache', $tester->getDisplay(), 'Key services are displayed');
}

public function testSearchArgument()
Expand Down
@@ -0,0 +1,38 @@
<?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\SecurityBundle\DependencyInjection;

use Symfony\Component\DependencyInjection\Debug\AutowiringInfoProviderInterface;
use Symfony\Component\DependencyInjection\Debug\AutowiringTypeInfo;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;

/**
* @author Ryan Weaver <ryan@knpuniversity.com>
*/
final class SecurityAutowiringInfoProvider implements AutowiringInfoProviderInterface
{
public function getTypeInfos(): array
{
return [
AutowiringTypeInfo::create(GuardAuthenticatorHandler::class, 'Guard Auth Handler')
->setDescription('use to manually authenticate with Guard'),

AutowiringTypeInfo::create(Security::class, 'Security')
->setDescription('use to check access & get the current User'),

AutowiringTypeInfo::create(UserPasswordEncoderInterface::class, 'Password Encoder')
->setDescription('use to encode passwords & check them'),
];
}
}
Expand Up @@ -19,5 +19,9 @@
</service>

<service id="security.firewall" alias="debug.security.firewall" />

<service id="debug.autowiring.security_autowiring_info_provider" class="Symfony\Bundle\SecurityBundle\DependencyInjection\SecurityAutowiringInfoProvider">
<tag name="debug.autowiring_info_provider" />
</service>
</services>
</container>

0 comments on commit 6e10bf2

Please sign in to comment.