From 423a54f46e68e18220cd1e65959e71d8a2eca9d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Bogusz?= Date: Mon, 14 Jan 2019 00:50:28 +0100 Subject: [PATCH] [Console] Added suggestions for missing packages --- .../SuggestMissingPackageSubscriber.php | 83 +++++++++++++++++++ .../Resources/config/console.xml | 4 + .../Tests/Console/ApplicationTest.php | 31 +++++++ 3 files changed, 118 insertions(+) create mode 100644 src/Symfony/Bundle/FrameworkBundle/EventListener/SuggestMissingPackageSubscriber.php diff --git a/src/Symfony/Bundle/FrameworkBundle/EventListener/SuggestMissingPackageSubscriber.php b/src/Symfony/Bundle/FrameworkBundle/EventListener/SuggestMissingPackageSubscriber.php new file mode 100644 index 0000000000..692a878a9d --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/EventListener/SuggestMissingPackageSubscriber.php @@ -0,0 +1,83 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\FrameworkBundle\EventListener; + +use Symfony\Component\Console\ConsoleEvents; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Exception\CommandNotFoundException; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +/** + * Suggests a package, that should be installed (via composer), + * if the package is missing, and the input command namespace can be mapped to a Symfony bundle. + * + * @author Przemysław Bogusz + * + * @internal + */ +final class SuggestMissingPackageSubscriber implements EventSubscriberInterface +{ + private const PACKAGES = [ + 'doctrine' => [ + 'fixtures' => ['DoctrineFixturesBundle', 'doctrine/doctrine-fixtures-bundle --dev'], + 'mongodb' => ['DoctrineMongoDBBundle', 'doctrine/mongodb-odm-bundle'], + '_default' => ['Doctrine ORM', 'symfony/orm-pack'], + ], + 'generate' => [ + '_default' => ['SensioGeneratorBundle', 'sensio/generator-bundle'], + ], + 'make' => [ + '_default' => ['MakerBundle', 'symfony/maker-bundle --dev'], + ], + 'server' => [ + 'dump' => ['VarDumper Component', 'symfony/var-dumper --dev'], + '_default' => ['WebServerBundle', 'symfony/web-server-bundle --dev'], + ], + ]; + + public function onConsoleError(ConsoleErrorEvent $event): void + { + if (!$event->getError() instanceof CommandNotFoundException) { + return; + } + + [$namespace, $command] = explode(':', $event->getInput()->getFirstArgument()) + [1 => '']; + + if (!isset(self::PACKAGES[$namespace])) { + return; + } + + if (isset(self::PACKAGES[$namespace][$command])) { + $suggestion = self::PACKAGES[$namespace][$command]; + $exact = true; + } else { + $suggestion = self::PACKAGES[$namespace]['_default']; + $exact = false; + } + + $error = $event->getError(); + + if ($error->getAlternatives() && !$exact) { + return; + } + + $message = sprintf("%s\n\nYou may be looking for a command provided by the \"%s\" which is currently not installed. Try running \"composer require %s\".", $error->getMessage(), $suggestion[0], $suggestion[1]); + $event->setError(new CommandNotFoundException($message)); + } + + public static function getSubscribedEvents(): array + { + return [ + ConsoleEvents::ERROR => ['onConsoleError', 0], + ]; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml index 3d9f3a8188..f982810740 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml @@ -13,6 +13,10 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php index 6662efd4a4..5021d56733 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php @@ -12,8 +12,11 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Console; use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Bundle\FrameworkBundle\EventListener\SuggestMissingPackageSubscriber; use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Event\ConsoleErrorEvent; +use Symfony\Component\Console\Exception\CommandNotFoundException; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\NullOutput; @@ -226,6 +229,34 @@ class ApplicationTest extends TestCase $this->assertContains(trim('[WARNING] Some commands could not be registered:'), trim($display[1])); } + public function testSuggestingPackagesWithExactMatch() + { + $result = $this->createEventForSuggestingPackages('server:dump', []); + $this->assertRegExp('/You may be looking for a command provided by/', $result); + } + + public function testSuggestingPackagesWithPartialMatchAndNoAlternatives() + { + $result = $this->createEventForSuggestingPackages('server', []); + $this->assertRegExp('/You may be looking for a command provided by/', $result); + } + + public function testSuggestingPackagesWithPartialMatchAndAlternatives() + { + $result = $this->createEventForSuggestingPackages('server', ['server:run']); + $this->assertNotRegExp('/You may be looking for a command provided by/', $result); + } + + private function createEventForSuggestingPackages(string $command, array $alternatives = []): string + { + $error = new CommandNotFoundException('', $alternatives); + $event = new ConsoleErrorEvent(new ArrayInput([$command]), new NullOutput(), $error); + $subscriber = new SuggestMissingPackageSubscriber(); + $subscriber->onConsoleError($event); + + return $event->getError()->getMessage(); + } + private function getKernel(array $bundles, $useDispatcher = false) { $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();