GeneratorCommand.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * This file is part of Hyperf.
  5. *
  6. * @link https://www.hyperf.io
  7. * @document https://hyperf.wiki
  8. * @contact group@hyperf.io
  9. * @license https://github.com/hyperf/hyperf/blob/master/LICENSE
  10. */
  11. namespace Hyperf\Devtool\Generator;
  12. use Hyperf\CodeParser\Project;
  13. use Hyperf\Collection\Arr;
  14. use Hyperf\Context\ApplicationContext;
  15. use Hyperf\Contract\ConfigInterface;
  16. use Hyperf\Stringable\Str;
  17. use Psr\Container\ContainerInterface;
  18. use Symfony\Component\Console\Command\Command;
  19. use Symfony\Component\Console\Input\InputArgument;
  20. use Symfony\Component\Console\Input\InputInterface;
  21. use Symfony\Component\Console\Input\InputOption;
  22. use Symfony\Component\Console\Output\OutputInterface;
  23. abstract class GeneratorCommand extends Command
  24. {
  25. protected ?InputInterface $input = null;
  26. protected ?OutputInterface $output = null;
  27. public function configure()
  28. {
  29. foreach ($this->getArguments() as $argument) {
  30. $this->addArgument(...$argument);
  31. }
  32. foreach ($this->getOptions() as $option) {
  33. $this->addOption(...$option);
  34. }
  35. }
  36. /**
  37. * Execute the console command.
  38. */
  39. public function execute(InputInterface $input, OutputInterface $output): int
  40. {
  41. $this->input = $input;
  42. $this->output = $output;
  43. $name = $this->qualifyClass($this->getNameInput());
  44. $path = $this->getPath($name);
  45. // First we will check to see if the class already exists. If it does, we don't want
  46. // to create the class and overwrite the user's code. So, we will bail out so the
  47. // code is untouched. Otherwise, we will continue generating this class' files.
  48. if (($input->getOption('force') === false) && $this->alreadyExists($this->getNameInput())) {
  49. $output->writeln(sprintf('<fg=red>%s</>', $name . ' already exists!'));
  50. return 0;
  51. }
  52. // Next, we will generate the path to the location where this class' file should get
  53. // written. Then, we will build the class and make the proper replacements on the
  54. // stub files so that it gets the correctly formatted namespace and class name.
  55. $this->makeDirectory($path);
  56. file_put_contents($path, $this->buildClass($name));
  57. $output->writeln(sprintf('<info>%s</info>', $name . ' created successfully.'));
  58. $this->openWithIde($path);
  59. return 0;
  60. }
  61. /**
  62. * Parse the class name and format according to the root namespace.
  63. */
  64. protected function qualifyClass(string $name): string
  65. {
  66. $name = ltrim($name, '\\/');
  67. $name = str_replace('/', '\\', $name);
  68. $namespace = $this->input->getOption('namespace');
  69. if (empty($namespace)) {
  70. $namespace = $this->getDefaultNamespace();
  71. }
  72. return $namespace . '\\' . $name;
  73. }
  74. /**
  75. * Determine if the class already exists.
  76. */
  77. protected function alreadyExists(string $rawName): bool
  78. {
  79. return is_file($this->getPath($this->qualifyClass($rawName)));
  80. }
  81. /**
  82. * Get the destination class path.
  83. */
  84. protected function getPath(string $name): string
  85. {
  86. $project = new Project();
  87. return BASE_PATH . '/' . $project->path($name);
  88. }
  89. /**
  90. * Build the directory for the class if necessary.
  91. */
  92. protected function makeDirectory(string $path): string
  93. {
  94. if (! is_dir(dirname($path))) {
  95. mkdir(dirname($path), 0777, true);
  96. }
  97. return $path;
  98. }
  99. /**
  100. * Build the class with the given name.
  101. */
  102. protected function buildClass(string $name): string
  103. {
  104. $stub = file_get_contents($this->getStub());
  105. return $this->replaceNamespace($stub, $name)->replaceClass($stub, $name);
  106. }
  107. /**
  108. * Replace the namespace for the given stub.
  109. */
  110. protected function replaceNamespace(string &$stub, string $name): static
  111. {
  112. $stub = str_replace(
  113. ['%NAMESPACE%'],
  114. [$this->getNamespace($name)],
  115. $stub
  116. );
  117. return $this;
  118. }
  119. /**
  120. * Get the full namespace for a given class, without the class name.
  121. */
  122. protected function getNamespace(string $name): string
  123. {
  124. return trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
  125. }
  126. /**
  127. * Replace the class name for the given stub.
  128. */
  129. protected function replaceClass(string $stub, string $name): string
  130. {
  131. $class = str_replace($this->getNamespace($name) . '\\', '', $name);
  132. return str_replace('%CLASS%', $class, $stub);
  133. }
  134. /**
  135. * Get the desired class name from the input.
  136. */
  137. protected function getNameInput(): string
  138. {
  139. return trim($this->input->getArgument('name'));
  140. }
  141. /**
  142. * Get the console command arguments.
  143. */
  144. protected function getArguments(): array
  145. {
  146. return [
  147. ['name', InputArgument::REQUIRED, 'The name of the class'],
  148. ];
  149. }
  150. /**
  151. * Get the console command options.
  152. */
  153. protected function getOptions(): array
  154. {
  155. return [
  156. ['force', 'f', InputOption::VALUE_NONE, 'Whether force to rewrite.'],
  157. ['namespace', 'N', InputOption::VALUE_OPTIONAL, 'The namespace for class.', null],
  158. ];
  159. }
  160. /**
  161. * Get the custom config for generator.
  162. */
  163. protected function getConfig(): array
  164. {
  165. $class = Arr::last(explode('\\', static::class));
  166. $class = Str::replaceLast('Command', '', $class);
  167. $key = 'devtool.generator.' . Str::snake($class, '.');
  168. return $this->getContainer()->get(ConfigInterface::class)->get($key) ?? [];
  169. }
  170. protected function getContainer(): ContainerInterface
  171. {
  172. return ApplicationContext::getContainer();
  173. }
  174. /**
  175. * Get the stub file for the generator.
  176. */
  177. abstract protected function getStub(): string;
  178. /**
  179. * Get the default namespace for the class.
  180. */
  181. abstract protected function getDefaultNamespace(): string;
  182. /**
  183. * Get the editor file opener URL by its name.
  184. */
  185. protected function getEditorUrl(string $ide): string
  186. {
  187. switch ($ide) {
  188. case 'sublime':
  189. return 'subl://open?url=file://%s';
  190. case 'textmate':
  191. return 'txmt://open?url=file://%s';
  192. case 'emacs':
  193. return 'emacs://open?url=file://%s';
  194. case 'macvim':
  195. return 'mvim://open/?url=file://%s';
  196. case 'phpstorm':
  197. return 'phpstorm://open?file=%s';
  198. case 'idea':
  199. return 'idea://open?file=%s';
  200. case 'vscode':
  201. return 'vscode://file/%s';
  202. case 'vscode-insiders':
  203. return 'vscode-insiders://file/%s';
  204. case 'vscode-remote':
  205. return 'vscode://vscode-remote/%s';
  206. case 'vscode-insiders-remote':
  207. return 'vscode-insiders://vscode-remote/%s';
  208. case 'atom':
  209. return 'atom://core/open/file?filename=%s';
  210. case 'nova':
  211. return 'nova://core/open/file?filename=%s';
  212. case 'netbeans':
  213. return 'netbeans://open/?f=%s';
  214. case 'xdebug':
  215. return 'xdebug://%s';
  216. default:
  217. return '';
  218. }
  219. }
  220. /**
  221. * Open resulted file path with the configured IDE.
  222. */
  223. protected function openWithIde(string $path): void
  224. {
  225. $ide = (string) $this->getContainer()->get(ConfigInterface::class)->get('devtool.ide');
  226. $openEditorUrl = $this->getEditorUrl($ide);
  227. if (! $openEditorUrl) {
  228. return;
  229. }
  230. $url = sprintf($openEditorUrl, $path);
  231. switch (PHP_OS_FAMILY) {
  232. case 'Windows':
  233. exec('explorer ' . $url);
  234. break;
  235. case 'Linux':
  236. exec('xdg-open ' . $url);
  237. break;
  238. case 'Darwin':
  239. exec('open ' . $url);
  240. break;
  241. }
  242. }
  243. }