CommandScanner.php 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice.
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  12. * @link http://cakephp.org CakePHP(tm) Project
  13. * @since 3.5.0
  14. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Console;
  17. use Cake\Core\App;
  18. use Cake\Core\Configure;
  19. use Cake\Core\Plugin;
  20. use Cake\Filesystem\Filesystem;
  21. use Cake\Utility\Inflector;
  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(): array
  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(): array
  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(string $plugin): array
  80. {
  81. if (!Plugin::isLoaded($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(string $path, string $namespace, string $prefix, array $hide): array
  102. {
  103. if (!is_dir($path)) {
  104. return [];
  105. }
  106. $classPattern = '/(Shell|Command)\.php$/';
  107. $fs = new Filesystem();
  108. $files = $fs->find($path, $classPattern);
  109. $shells = [];
  110. foreach ($files as $fileInfo) {
  111. $file = $fileInfo->getFilename();
  112. $name = Inflector::underscore(preg_replace($classPattern, '', $file));
  113. if (in_array($name, $hide, true)) {
  114. continue;
  115. }
  116. $class = $namespace . $fileInfo->getBasename('.php');
  117. /** @psalm-suppress DeprecatedClass */
  118. if (!is_subclass_of($class, Shell::class)
  119. && !is_subclass_of($class, Command::class)
  120. ) {
  121. continue;
  122. }
  123. if (is_subclass_of($class, Command::class)) {
  124. $name = $class::defaultName();
  125. }
  126. $shells[$path . $file] = [
  127. 'file' => $path . $file,
  128. 'fullName' => $prefix . $name,
  129. 'name' => $name,
  130. 'class' => $class,
  131. ];
  132. }
  133. ksort($shells);
  134. return array_values($shells);
  135. }
  136. }