ShellDispatcher.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 2.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Console;
  16. use Cake\Console\ConsoleIo;
  17. use Cake\Console\Exception\MissingShellException;
  18. use Cake\Core\App;
  19. use Cake\Core\Configure;
  20. use Cake\Core\Exception\Exception;
  21. use Cake\Core\Plugin;
  22. use Cake\Log\Log;
  23. use Cake\Shell\Task\CommandTask;
  24. use Cake\Utility\Inflector;
  25. /**
  26. * Shell dispatcher handles dispatching cli commands.
  27. *
  28. * Consult /bin/cake.php for how this class is used in practice.
  29. */
  30. class ShellDispatcher
  31. {
  32. /**
  33. * Contains arguments parsed from the command line.
  34. *
  35. * @var array
  36. */
  37. public $args = [];
  38. /**
  39. * List of connected aliases.
  40. *
  41. * @var array
  42. */
  43. protected static $_aliases = [];
  44. /**
  45. * Constructor
  46. *
  47. * The execution of the script is stopped after dispatching the request with
  48. * a status code of either 0 or 1 according to the result of the dispatch.
  49. *
  50. * @param array $args the argv from PHP
  51. * @param bool $bootstrap Should the environment be bootstrapped.
  52. */
  53. public function __construct($args = [], $bootstrap = true)
  54. {
  55. set_time_limit(0);
  56. $this->args = (array)$args;
  57. $this->addShortPluginAliases();
  58. if ($bootstrap) {
  59. $this->_initEnvironment();
  60. }
  61. }
  62. /**
  63. * Add an alias for a shell command.
  64. *
  65. * Aliases allow you to call shells by alternate names. This is most
  66. * useful when dealing with plugin shells that you want to have shorter
  67. * names for.
  68. *
  69. * If you re-use an alias the last alias set will be the one available.
  70. *
  71. * ### Usage
  72. *
  73. * Aliasing a shell named ClassName:
  74. *
  75. * ```
  76. * $this->alias('alias', 'ClassName');
  77. * ```
  78. *
  79. * Getting the original name for a given alias:
  80. *
  81. * ```
  82. * $this->alias('alias');
  83. * ```
  84. *
  85. * @param string $short The new short name for the shell.
  86. * @param string|null $original The original full name for the shell.
  87. * @return string|false The aliased class name, or false if the alias does not exist
  88. */
  89. public static function alias($short, $original = null)
  90. {
  91. $short = Inflector::camelize($short);
  92. if ($original) {
  93. static::$_aliases[$short] = $original;
  94. }
  95. return isset(static::$_aliases[$short]) ? static::$_aliases[$short] : false;
  96. }
  97. /**
  98. * Clear any aliases that have been set.
  99. *
  100. * @return void
  101. */
  102. public static function resetAliases()
  103. {
  104. static::$_aliases = [];
  105. }
  106. /**
  107. * Run the dispatcher
  108. *
  109. * @param array $argv The argv from PHP
  110. * @return int The exit code of the shell process.
  111. */
  112. public static function run($argv)
  113. {
  114. $dispatcher = new ShellDispatcher($argv);
  115. return $dispatcher->dispatch();
  116. }
  117. /**
  118. * Defines current working environment.
  119. *
  120. * @return void
  121. * @throws \Cake\Core\Exception\Exception
  122. */
  123. protected function _initEnvironment()
  124. {
  125. if (!$this->_bootstrap()) {
  126. $message = "Unable to load CakePHP core.\nMake sure Cake exists in " . CAKE_CORE_INCLUDE_PATH;
  127. throw new Exception($message);
  128. }
  129. if (function_exists('ini_set')) {
  130. ini_set('html_errors', false);
  131. ini_set('implicit_flush', true);
  132. ini_set('max_execution_time', 0);
  133. }
  134. $this->shiftArgs();
  135. }
  136. /**
  137. * Initializes the environment and loads the CakePHP core.
  138. *
  139. * @return bool Success.
  140. */
  141. protected function _bootstrap()
  142. {
  143. if (!Configure::read('App.fullBaseUrl')) {
  144. Configure::write('App.fullBaseUrl', 'http://localhost');
  145. }
  146. return true;
  147. }
  148. /**
  149. * Dispatches a CLI request
  150. *
  151. * Converts a shell command result into an exit code. Null/True
  152. * are treated as success. All other return values are an error.
  153. *
  154. * @return int The cli command exit code. 0 is success.
  155. */
  156. public function dispatch()
  157. {
  158. $result = $this->_dispatch();
  159. if ($result === null || $result === true) {
  160. return 0;
  161. }
  162. return 1;
  163. }
  164. /**
  165. * Dispatch a request.
  166. *
  167. * @return bool
  168. * @throws \Cake\Console\Exception\MissingShellMethodException
  169. */
  170. protected function _dispatch()
  171. {
  172. $shell = $this->shiftArgs();
  173. if (!$shell) {
  174. $this->help();
  175. return false;
  176. }
  177. if (in_array($shell, ['help', '--help', '-h'])) {
  178. $this->help();
  179. return true;
  180. }
  181. $Shell = $this->findShell($shell);
  182. $Shell->initialize();
  183. return $Shell->runCommand($this->args, true);
  184. }
  185. /**
  186. * For all loaded plugins, add a short alias
  187. *
  188. * This permits a plugin which implements a shell of the same name to be accessed
  189. * Using the shell name alone
  190. *
  191. * @return array the resultant list of aliases
  192. */
  193. public function addShortPluginAliases()
  194. {
  195. $plugins = Plugin::loaded();
  196. $io = new ConsoleIo();
  197. $task = new CommandTask($io);
  198. $io->setLoggers(false);
  199. $list = $task->getShellList() + ['app' => []];
  200. $fixed = array_flip($list['app']) + array_flip($list['CORE']);
  201. $aliases = [];
  202. foreach ($plugins as $plugin) {
  203. if (!isset($list[$plugin])) {
  204. continue;
  205. }
  206. foreach ($list[$plugin] as $shell) {
  207. $aliases += [$shell => $plugin];
  208. }
  209. }
  210. foreach ($aliases as $shell => $plugin) {
  211. if (isset($fixed[$shell])) {
  212. Log::write(
  213. 'debug',
  214. "command '$shell' in plugin '$plugin' was not aliased, conflicts with another shell",
  215. ['shell-dispatcher']
  216. );
  217. continue;
  218. }
  219. $other = static::alias($shell);
  220. if ($other) {
  221. $other = $aliases[$shell];
  222. Log::write(
  223. 'debug',
  224. "command '$shell' in plugin '$plugin' was not aliased, conflicts with '$other'",
  225. ['shell-dispatcher']
  226. );
  227. continue;
  228. }
  229. static::alias($shell, "$plugin.$shell");
  230. }
  231. return static::$_aliases;
  232. }
  233. /**
  234. * Get shell to use, either plugin shell or application shell
  235. *
  236. * All paths in the loaded shell paths are searched, handles alias
  237. * dereferencing
  238. *
  239. * @param string $shell Optionally the name of a plugin
  240. * @return \Cake\Console\Shell A shell instance.
  241. * @throws \Cake\Console\Exception\MissingShellException when errors are encountered.
  242. */
  243. public function findShell($shell)
  244. {
  245. $className = $this->_shellExists($shell);
  246. if (!$className) {
  247. $shell = $this->_handleAlias($shell);
  248. $className = $this->_shellExists($shell);
  249. }
  250. if (!$className) {
  251. throw new MissingShellException([
  252. 'class' => $shell,
  253. ]);
  254. }
  255. return $this->_createShell($className, $shell);
  256. }
  257. /**
  258. * If the input matches an alias, return the aliased shell name
  259. *
  260. * @param string $shell Optionally the name of a plugin or alias
  261. * @return string Shell name with plugin prefix
  262. */
  263. protected function _handleAlias($shell)
  264. {
  265. $aliased = static::alias($shell);
  266. if ($aliased) {
  267. $shell = $aliased;
  268. }
  269. $class = array_map('Cake\Utility\Inflector::camelize', explode('.', $shell));
  270. return implode('.', $class);
  271. }
  272. /**
  273. * Check if a shell class exists for the given name.
  274. *
  275. * @param string $shell The shell name to look for.
  276. * @return string|bool Either the classname or false.
  277. */
  278. protected function _shellExists($shell)
  279. {
  280. $class = App::className($shell, 'Shell', 'Shell');
  281. if (class_exists($class)) {
  282. return $class;
  283. }
  284. return false;
  285. }
  286. /**
  287. * Create the given shell name, and set the plugin property
  288. *
  289. * @param string $className The class name to instantiate
  290. * @param string $shortName The plugin-prefixed shell name
  291. * @return \Cake\Console\Shell A shell instance.
  292. */
  293. protected function _createShell($className, $shortName)
  294. {
  295. list($plugin) = pluginSplit($shortName);
  296. $instance = new $className();
  297. $instance->plugin = trim($plugin, '.');
  298. return $instance;
  299. }
  300. /**
  301. * Removes first argument and shifts other arguments up
  302. *
  303. * @return mixed Null if there are no arguments otherwise the shifted argument
  304. */
  305. public function shiftArgs()
  306. {
  307. return array_shift($this->args);
  308. }
  309. /**
  310. * Shows console help. Performs an internal dispatch to the CommandList Shell
  311. *
  312. * @return void
  313. */
  314. public function help()
  315. {
  316. $this->args = array_merge(['command_list'], $this->args);
  317. $this->dispatch();
  318. }
  319. }