CommandScanner.php 4.4 KB

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