CommandScanner.php 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  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 3.5.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Console;
  16. use Cake\Core\App;
  17. use Cake\Core\Configure;
  18. use Cake\Core\Plugin;
  19. use Cake\Filesystem\Folder;
  20. use Cake\Utility\Inflector;
  21. /**
  22. * Used by CommandCollection and CommandTask to scan the filesystem
  23. * for command classes.
  24. *
  25. * @internal
  26. */
  27. class CommandScanner
  28. {
  29. /**
  30. * Scan CakePHP core, the applications and plugins for shell classes
  31. *
  32. * @return array
  33. */
  34. public function scanAll()
  35. {
  36. $shellList = [
  37. 'CORE' => $this->scanCore(),
  38. 'app' => $this->scanApp(),
  39. 'plugins' => $this->scanPlugins()
  40. ];
  41. return $shellList;
  42. }
  43. /**
  44. * Scan CakePHP internals for shells & commands.
  45. *
  46. * @return array A list of command metadata.
  47. */
  48. protected function scanCore()
  49. {
  50. $coreShells = $this->scanDir(
  51. dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Shell' . DIRECTORY_SEPARATOR,
  52. 'Cake\Shell\\',
  53. '',
  54. ['command_list']
  55. );
  56. $coreCommands = $this->scanDir(
  57. dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Command' . DIRECTORY_SEPARATOR,
  58. 'Cake\Command\\',
  59. '',
  60. ['command_list']
  61. );
  62. return array_merge($coreShells, $coreCommands);
  63. }
  64. /**
  65. * Scan the application for shells & commands.
  66. *
  67. * @return array A list of command metadata.
  68. */
  69. protected function scanApp()
  70. {
  71. $appNamespace = Configure::read('App.namespace');
  72. $appShells = $this->scanDir(
  73. App::path('Shell')[0],
  74. $appNamespace . '\Shell\\',
  75. '',
  76. ['app']
  77. );
  78. $appCommands = $this->scanDir(
  79. App::path('Command')[0],
  80. $appNamespace . '\Command\\',
  81. '',
  82. ['app']
  83. );
  84. return array_merge($appShells, $appCommands);
  85. }
  86. /**
  87. * Scan the plugins for shells & commands.
  88. *
  89. * @return array A list of command metadata.
  90. */
  91. protected function scanPlugins()
  92. {
  93. $plugins = [];
  94. foreach (Plugin::loaded() as $plugin) {
  95. $path = Plugin::classPath($plugin);
  96. $namespace = str_replace('/', '\\', $plugin);
  97. $prefix = Inflector::underscore($plugin) . '.';
  98. $commands = $this->scanDir($path . 'Command', $namespace . '\Command\\', $prefix, []);
  99. $shells = $this->scanDir($path . 'Shell', $namespace . '\Shell\\', $prefix, []);
  100. $plugins[$plugin] = array_merge($shells, $commands);
  101. }
  102. return $plugins;
  103. }
  104. /**
  105. * Scan a directory for .php files and return the class names that
  106. * should be within them.
  107. *
  108. * @param string $path The directory to read.
  109. * @param string $namespace The namespace the shells live in.
  110. * @param string $prefix The prefix to apply to commands for their full name.
  111. * @param array $hide A list of command names to hide as they are internal commands.
  112. * @return array The list of shell info arrays based on scanning the filesystem and inflection.
  113. */
  114. protected function scanDir($path, $namespace, $prefix, array $hide)
  115. {
  116. $dir = new Folder($path);
  117. $contents = $dir->read(true, true);
  118. if (empty($contents[1])) {
  119. return [];
  120. }
  121. $classPattern = '/(Shell|Command)$/';
  122. $shells = [];
  123. foreach ($contents[1] as $file) {
  124. if (substr($file, -4) !== '.php') {
  125. continue;
  126. }
  127. $shell = substr($file, 0, -4);
  128. if (!preg_match($classPattern, $shell)) {
  129. continue;
  130. }
  131. $name = Inflector::underscore(preg_replace($classPattern, '', $shell));
  132. if (in_array($name, $hide, true)) {
  133. continue;
  134. }
  135. $class = $namespace . $shell;
  136. if (!is_subclass_of($class, Shell::class) && !is_subclass_of($class, Command::class)) {
  137. continue;
  138. }
  139. $shells[] = [
  140. 'file' => $path . $file,
  141. 'fullName' => $prefix . $name,
  142. 'name' => $name,
  143. 'class' => $class
  144. ];
  145. }
  146. return $shells;
  147. }
  148. }