mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-11-04 06:37:55 +08:00 
			
		
		
		
	implement suggestion for unknown command in console application
suggestion is based on two principles: - first suggest commands the begin with the unknown name, to suggest commands after accidentally hitting enter - second find similar commands by computing the levenshtein distance which is a measurement on how many changes need to be made to convert one string into another. This is perfect for finding typos.
This commit is contained in:
		@ -32,9 +32,9 @@ class ErrorHandler extends \yii\base\ErrorHandler
 | 
			
		||||
        if ($exception instanceof UnknownCommandException) {
 | 
			
		||||
            // display message and suggest alternatives in case of unknown command
 | 
			
		||||
            $message = $this->formatMessage($exception->getName() . ': ') . $exception->command;
 | 
			
		||||
            $alternatives = $exception->suggestAlternatives();
 | 
			
		||||
            if (count($alternatives) == 1) {
 | 
			
		||||
                $message .= "\n\nDid you mean  " . reset($alternatives) . " ?";
 | 
			
		||||
            $alternatives = $exception->getSuggestedAlternatives();
 | 
			
		||||
            if (count($alternatives) === 1) {
 | 
			
		||||
                $message .= "\n\nDid you mean \"" . reset($alternatives) . "\"?";
 | 
			
		||||
            } elseif (count($alternatives) > 1) {
 | 
			
		||||
                $message .= "\n\nDid you mean one of these?\n    - " . implode("\n    - ", $alternatives);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -6,22 +6,35 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace yii\console;
 | 
			
		||||
 | 
			
		||||
use yii\console\controllers\HelpController;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Exception represents an exception caused by incorrect usage of a console command.
 | 
			
		||||
 * UnknownCommandException represents an exception caused by incorrect usage of a console command.
 | 
			
		||||
 *
 | 
			
		||||
 * @author Carsten Brandt <mail@cebe.cc>
 | 
			
		||||
 * @since 2.0.11
 | 
			
		||||
 */
 | 
			
		||||
class UnknownCommandException extends Exception
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @var string the name of the command that could not be recognized.
 | 
			
		||||
     */
 | 
			
		||||
    public $command;
 | 
			
		||||
    /**
 | 
			
		||||
     * @var Application
 | 
			
		||||
     */
 | 
			
		||||
    public $application;
 | 
			
		||||
    protected $application;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Construct the exception.
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $route the route of the command that could not be found.
 | 
			
		||||
     * @param Application $application the console application instance involved.
 | 
			
		||||
     * @param int $code the Exception code.
 | 
			
		||||
     * @param \Exception $previous the previous exception used for the exception chaining.
 | 
			
		||||
     */
 | 
			
		||||
    public function __construct($route, $application, $code = 0, \Exception $previous = null)
 | 
			
		||||
    {
 | 
			
		||||
        $this->command = $route;
 | 
			
		||||
@ -37,7 +50,20 @@ class UnknownCommandException extends Exception
 | 
			
		||||
        return 'Unknown command';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function suggestAlternatives()
 | 
			
		||||
    /**
 | 
			
		||||
     * Suggest alternative commands for [[$command]] based on string similarity.
 | 
			
		||||
     *
 | 
			
		||||
     * Alternatives are searched using the following steps:
 | 
			
		||||
     *
 | 
			
		||||
     * - suggest alternatives that begin with `$command`
 | 
			
		||||
     * - find typos by calculating the Levenshtein distance between the unknown command and all
 | 
			
		||||
     *   available commands. The Levenshtein distance is defined as the minimal number of
 | 
			
		||||
     *   characters you have to replace, insert or delete to transform str1 into str2.
 | 
			
		||||
     *
 | 
			
		||||
     * @see http://php.net/manual/en/function.levenshtein.php
 | 
			
		||||
     * @return array a list of suggested alternatives sorted by similarity.
 | 
			
		||||
     */
 | 
			
		||||
    public function getSuggestedAlternatives()
 | 
			
		||||
    {
 | 
			
		||||
        $help = $this->application->createController('help');
 | 
			
		||||
        if ($help === false) {
 | 
			
		||||
@ -67,15 +93,49 @@ class UnknownCommandException extends Exception
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        $availableActions = $this->filterBySimilarity($availableActions);
 | 
			
		||||
 | 
			
		||||
        asort($availableActions);
 | 
			
		||||
        return $availableActions;
 | 
			
		||||
        return $this->filterBySimilarity($availableActions, $this->command);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function filterBySimilarity($actions)
 | 
			
		||||
    /**
 | 
			
		||||
     * Find suggest alternative commands based on string similarity.
 | 
			
		||||
     *
 | 
			
		||||
     * Alternatives are searched using the following steps:
 | 
			
		||||
     *
 | 
			
		||||
     * - suggest alternatives that begin with `$command`
 | 
			
		||||
     * - find typos by calculating the Levenshtein distance between the unknown command and all
 | 
			
		||||
     *   available commands. The Levenshtein distance is defined as the minimal number of
 | 
			
		||||
     *   characters you have to replace, insert or delete to transform str1 into str2.
 | 
			
		||||
     *
 | 
			
		||||
     * @see http://php.net/manual/en/function.levenshtein.php
 | 
			
		||||
     * @param array $actions available command names.
 | 
			
		||||
     * @param string $command the command to compare to.
 | 
			
		||||
     * @return array a list of suggested alternatives sorted by similarity.
 | 
			
		||||
     */
 | 
			
		||||
    private function filterBySimilarity($actions, $command)
 | 
			
		||||
    {
 | 
			
		||||
        // TODO
 | 
			
		||||
        return $actions;
 | 
			
		||||
        $alternatives = [];
 | 
			
		||||
 | 
			
		||||
        // suggest alternatives that begin with $command first
 | 
			
		||||
        foreach ($actions as $action) {
 | 
			
		||||
            if (strpos($action, $command) === 0) {
 | 
			
		||||
                $alternatives[] = $action;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // calculate the Levenshtein distance between the unknown command and all available commands.
 | 
			
		||||
        $distances = array_map(function($action) use ($command) {
 | 
			
		||||
            $action = strlen($action) > 255 ? substr($action, 0, 255) : $action;
 | 
			
		||||
            $command = strlen($command) > 255 ? substr($command, 0, 255) : $command;
 | 
			
		||||
            return levenshtein($action, $command);
 | 
			
		||||
        }, array_combine($actions, $actions));
 | 
			
		||||
 | 
			
		||||
        // we assume a typo if the levensthein distance is no more than 3, i.e. 3 replacements needed
 | 
			
		||||
        $relevantTypos = array_filter($distances, function($distance) {
 | 
			
		||||
            return $distance <= 3;
 | 
			
		||||
        });
 | 
			
		||||
        asort($relevantTypos);
 | 
			
		||||
        $alternatives = array_merge($alternatives, array_flip($relevantTypos));
 | 
			
		||||
 | 
			
		||||
        return array_unique($alternatives);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user