UnknownCommandException.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\console;
  8. use yii\console\controllers\HelpController;
  9. /**
  10. * UnknownCommandException represents an exception caused by incorrect usage of a console command.
  11. *
  12. * @author Carsten Brandt <mail@cebe.cc>
  13. * @since 2.0.11
  14. */
  15. class UnknownCommandException extends Exception
  16. {
  17. /**
  18. * @var string the name of the command that could not be recognized.
  19. */
  20. public $command;
  21. /**
  22. * @var Application
  23. */
  24. protected $application;
  25. /**
  26. * Construct the exception.
  27. *
  28. * @param string $route the route of the command that could not be found.
  29. * @param Application $application the console application instance involved.
  30. * @param int $code the Exception code.
  31. * @param \Exception $previous the previous exception used for the exception chaining.
  32. */
  33. public function __construct($route, $application, $code = 0, \Exception $previous = null)
  34. {
  35. $this->command = $route;
  36. $this->application = $application;
  37. parent::__construct("Unknown command \"$route\".", $code, $previous);
  38. }
  39. /**
  40. * @return string the user-friendly name of this exception
  41. */
  42. public function getName()
  43. {
  44. return 'Unknown command';
  45. }
  46. /**
  47. * Suggest alternative commands for [[$command]] based on string similarity.
  48. *
  49. * Alternatives are searched using the following steps:
  50. *
  51. * - suggest alternatives that begin with `$command`
  52. * - find typos by calculating the Levenshtein distance between the unknown command and all
  53. * available commands. The Levenshtein distance is defined as the minimal number of
  54. * characters you have to replace, insert or delete to transform str1 into str2.
  55. *
  56. * @see http://php.net/manual/en/function.levenshtein.php
  57. * @return array a list of suggested alternatives sorted by similarity.
  58. */
  59. public function getSuggestedAlternatives()
  60. {
  61. $help = $this->application->createController('help');
  62. if ($help === false) {
  63. return [];
  64. }
  65. /** @var $helpController HelpController */
  66. list($helpController, $actionID) = $help;
  67. $availableActions = [];
  68. $commands = $helpController->getCommands();
  69. foreach ($commands as $command) {
  70. $result = $this->application->createController($command);
  71. if ($result === false) {
  72. continue;
  73. }
  74. // add the command itself (default action)
  75. $availableActions[] = $command;
  76. // add all actions of this controller
  77. /** @var $controller Controller */
  78. list($controller, $actionID) = $result;
  79. $actions = $helpController->getActions($controller);
  80. if (!empty($actions)) {
  81. $prefix = $controller->getUniqueId();
  82. foreach ($actions as $action) {
  83. $availableActions[] = $prefix . '/' . $action;
  84. }
  85. }
  86. }
  87. return $this->filterBySimilarity($availableActions, $this->command);
  88. }
  89. /**
  90. * Find suggest alternative commands based on string similarity.
  91. *
  92. * Alternatives are searched using the following steps:
  93. *
  94. * - suggest alternatives that begin with `$command`
  95. * - find typos by calculating the Levenshtein distance between the unknown command and all
  96. * available commands. The Levenshtein distance is defined as the minimal number of
  97. * characters you have to replace, insert or delete to transform str1 into str2.
  98. *
  99. * @see http://php.net/manual/en/function.levenshtein.php
  100. * @param array $actions available command names.
  101. * @param string $command the command to compare to.
  102. * @return array a list of suggested alternatives sorted by similarity.
  103. */
  104. private function filterBySimilarity($actions, $command)
  105. {
  106. $alternatives = [];
  107. // suggest alternatives that begin with $command first
  108. foreach ($actions as $action) {
  109. if (strpos($action, $command) === 0) {
  110. $alternatives[] = $action;
  111. }
  112. }
  113. // calculate the Levenshtein distance between the unknown command and all available commands.
  114. $distances = array_map(function($action) use ($command) {
  115. $action = strlen($action) > 255 ? substr($action, 0, 255) : $action;
  116. $command = strlen($command) > 255 ? substr($command, 0, 255) : $command;
  117. return levenshtein($action, $command);
  118. }, array_combine($actions, $actions));
  119. // we assume a typo if the levensthein distance is no more than 3, i.e. 3 replacements needed
  120. $relevantTypos = array_filter($distances, function($distance) {
  121. return $distance <= 3;
  122. });
  123. asort($relevantTypos);
  124. $alternatives = array_merge($alternatives, array_flip($relevantTypos));
  125. return array_unique($alternatives);
  126. }
  127. }