Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 3560cfd

Browse files
committed
feature #29865 [Console] Added suggestions for missing packages (przemyslaw-bogusz)
This PR was squashed before being merged into the 4.3-dev branch (closes #29865). Discussion ---------- [Console] Added suggestions for missing packages | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | License | MIT Currently, when someone runs one of the most common commands, e.g. `server:run`, but does not have a required package installed, they will get a general **'There are no commands defined...'** message. This commit adds a more useful message, informing the user about a package that might be missing and suggesting a command that should be run in order to install it, e.g. `composer require symfony/web-server-bundle --dev`. Commits ------- 423a54f [Console] Added suggestions for missing packages
2 parents cbe8cff + 423a54f commit 3560cfd

File tree

3 files changed

+118
-0
lines changed

3 files changed

+118
-0
lines changed
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\EventListener;
13+
14+
use Symfony\Component\Console\ConsoleEvents;
15+
use Symfony\Component\Console\Event\ConsoleErrorEvent;
16+
use Symfony\Component\Console\Exception\CommandNotFoundException;
17+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
18+
19+
/**
20+
* Suggests a package, that should be installed (via composer),
21+
* if the package is missing, and the input command namespace can be mapped to a Symfony bundle.
22+
*
23+
* @author Przemysław Bogusz <[email protected]>
24+
*
25+
* @internal
26+
*/
27+
final class SuggestMissingPackageSubscriber implements EventSubscriberInterface
28+
{
29+
private const PACKAGES = [
30+
'doctrine' => [
31+
'fixtures' => ['DoctrineFixturesBundle', 'doctrine/doctrine-fixtures-bundle --dev'],
32+
'mongodb' => ['DoctrineMongoDBBundle', 'doctrine/mongodb-odm-bundle'],
33+
'_default' => ['Doctrine ORM', 'symfony/orm-pack'],
34+
],
35+
'generate' => [
36+
'_default' => ['SensioGeneratorBundle', 'sensio/generator-bundle'],
37+
],
38+
'make' => [
39+
'_default' => ['MakerBundle', 'symfony/maker-bundle --dev'],
40+
],
41+
'server' => [
42+
'dump' => ['VarDumper Component', 'symfony/var-dumper --dev'],
43+
'_default' => ['WebServerBundle', 'symfony/web-server-bundle --dev'],
44+
],
45+
];
46+
47+
public function onConsoleError(ConsoleErrorEvent $event): void
48+
{
49+
if (!$event->getError() instanceof CommandNotFoundException) {
50+
return;
51+
}
52+
53+
[$namespace, $command] = explode(':', $event->getInput()->getFirstArgument()) + [1 => ''];
54+
55+
if (!isset(self::PACKAGES[$namespace])) {
56+
return;
57+
}
58+
59+
if (isset(self::PACKAGES[$namespace][$command])) {
60+
$suggestion = self::PACKAGES[$namespace][$command];
61+
$exact = true;
62+
} else {
63+
$suggestion = self::PACKAGES[$namespace]['_default'];
64+
$exact = false;
65+
}
66+
67+
$error = $event->getError();
68+
69+
if ($error->getAlternatives() && !$exact) {
70+
return;
71+
}
72+
73+
$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]);
74+
$event->setError(new CommandNotFoundException($message));
75+
}
76+
77+
public static function getSubscribedEvents(): array
78+
{
79+
return [
80+
ConsoleEvents::ERROR => ['onConsoleError', 0],
81+
];
82+
}
83+
}

src/Symfony/Bundle/FrameworkBundle/Resources/config/console.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
<tag name="monolog.logger" channel="console" />
1414
</service>
1515

16+
<service id="console.suggest_missing_package_subscriber" class="Symfony\Bundle\FrameworkBundle\EventListener\SuggestMissingPackageSubscriber">
17+
<tag name="kernel.event_subscriber" />
18+
</service>
19+
1620
<service id="console.command.about" class="Symfony\Bundle\FrameworkBundle\Command\AboutCommand">
1721
<tag name="console.command" command="about" />
1822
</service>

src/Symfony/Bundle/FrameworkBundle/Tests/Console/ApplicationTest.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@
1212
namespace Symfony\Bundle\FrameworkBundle\Tests\Console;
1313

1414
use Symfony\Bundle\FrameworkBundle\Console\Application;
15+
use Symfony\Bundle\FrameworkBundle\EventListener\SuggestMissingPackageSubscriber;
1516
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
1617
use Symfony\Component\Console\Command\Command;
18+
use Symfony\Component\Console\Event\ConsoleErrorEvent;
19+
use Symfony\Component\Console\Exception\CommandNotFoundException;
1720
use Symfony\Component\Console\Input\ArrayInput;
1821
use Symfony\Component\Console\Input\InputInterface;
1922
use Symfony\Component\Console\Output\NullOutput;
@@ -226,6 +229,34 @@ public function testRunOnlyWarnsOnUnregistrableCommandAtTheEnd()
226229
$this->assertContains(trim('[WARNING] Some commands could not be registered:'), trim($display[1]));
227230
}
228231

232+
public function testSuggestingPackagesWithExactMatch()
233+
{
234+
$result = $this->createEventForSuggestingPackages('server:dump', []);
235+
$this->assertRegExp('/You may be looking for a command provided by/', $result);
236+
}
237+
238+
public function testSuggestingPackagesWithPartialMatchAndNoAlternatives()
239+
{
240+
$result = $this->createEventForSuggestingPackages('server', []);
241+
$this->assertRegExp('/You may be looking for a command provided by/', $result);
242+
}
243+
244+
public function testSuggestingPackagesWithPartialMatchAndAlternatives()
245+
{
246+
$result = $this->createEventForSuggestingPackages('server', ['server:run']);
247+
$this->assertNotRegExp('/You may be looking for a command provided by/', $result);
248+
}
249+
250+
private function createEventForSuggestingPackages(string $command, array $alternatives = []): string
251+
{
252+
$error = new CommandNotFoundException('', $alternatives);
253+
$event = new ConsoleErrorEvent(new ArrayInput([$command]), new NullOutput(), $error);
254+
$subscriber = new SuggestMissingPackageSubscriber();
255+
$subscriber->onConsoleError($event);
256+
257+
return $event->getError()->getMessage();
258+
}
259+
229260
private function getKernel(array $bundles, $useDispatcher = false)
230261
{
231262
$container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerInterface')->getMock();

0 commit comments

Comments
 (0)