PhpCodeGenerator.php 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. <?php
  2. declare(strict_types=1);
  3. namespace SixShop\MakerBundle\Generator;
  4. use Symfony\Component\Console\Style\SymfonyStyle;
  5. class PhpCodeGenerator
  6. {
  7. private string $templatesPath;
  8. /**
  9. * 排除的模板文件,这些模板由专门的生成器处理(一对多关系)
  10. */
  11. private array $excludedTemplates = [
  12. 'src/Model/Model.php.tpl.php', // 模型模板 - 由 MigrationMaker 中的 ModelGenerator 处理
  13. 'src/Entity/Entity.php.tpl.php', // 实体模板 - 由 MigrationMaker 中的 EntityGenerator 处理
  14. 'src/Controller', // 控制器目录 - 由专门的控制器生成器处理
  15. 'database/migrations', // 迁移文件夹 - 由 MigrationGenerator 处理
  16. 'route', // 路由模板目录 - 由 RouteUpdater 处理
  17. ];
  18. public function __construct()
  19. {
  20. $this->templatesPath = __DIR__ . '/../../templates';
  21. }
  22. /**
  23. * 生成PHP代码文件
  24. */
  25. public function generatePhpFiles(string $packageName, string $namespace, string $id, string $description, string $targetPath, SymfonyStyle $io): bool
  26. {
  27. try {
  28. $basePath = $this->getBasePath($packageName, $targetPath);
  29. // 准备模板变量
  30. $variables = [
  31. 'namespace' => $namespace,
  32. 'name' => $packageName,
  33. 'id' => $id,
  34. 'description' => $description
  35. ];
  36. // 获取所有需要生成的PHP文件模板
  37. $templateFiles = $this->getTemplateFiles();
  38. $io->section('生成PHP代码文件:');
  39. foreach ($templateFiles as $templateFile) {
  40. if (!$this->generateFileFromTemplate($templateFile, $basePath, $variables, $io)) {
  41. return false;
  42. }
  43. }
  44. $io->success('PHP代码文件生成完成!');
  45. return true;
  46. } catch (\Exception $e) {
  47. $io->error('生成PHP代码文件时发生错误: ' . $e->getMessage());
  48. return false;
  49. }
  50. }
  51. /**
  52. * 获取扩展基础路径
  53. */
  54. private function getBasePath(string $packageName, ?string $targetPath = null): string
  55. {
  56. if ($targetPath !== null) {
  57. return $targetPath;
  58. }
  59. // 默认路径
  60. $vendorDir = dirname(__DIR__, 5);
  61. return $vendorDir . '/runtime/extension/' . $packageName;
  62. }
  63. /**
  64. * 获取所有模板文件
  65. */
  66. private function getTemplateFiles(): array
  67. {
  68. $templates = [];
  69. $this->scanTemplateDirectory($this->templatesPath, '', $templates);
  70. return $templates;
  71. }
  72. /**
  73. * 递归扫描模板目录
  74. */
  75. private function scanTemplateDirectory(string $directory, string $relativePath, array &$templates): void
  76. {
  77. if (!is_dir($directory)) {
  78. return;
  79. }
  80. $items = scandir($directory);
  81. foreach ($items as $item) {
  82. if ($item === '.' || $item === '..') {
  83. continue;
  84. }
  85. $fullPath = $directory . '/' . $item;
  86. $currentRelativePath = $relativePath ? $relativePath . '/' . $item : $item;
  87. if (is_dir($fullPath)) {
  88. // 检查是否为排除的目录
  89. if ($this->isExcludedTemplate($currentRelativePath)) {
  90. continue;
  91. }
  92. // 递归扫描子目录
  93. $this->scanTemplateDirectory($fullPath, $currentRelativePath, $templates);
  94. } elseif (is_file($fullPath) && str_ends_with($item, '.php.tpl.php')) {
  95. // 检查是否为排除的模板文件
  96. if ($this->isExcludedTemplate($currentRelativePath)) {
  97. continue;
  98. }
  99. // 添加模板文件
  100. $templates[] = [
  101. 'template' => $currentRelativePath,
  102. 'target' => str_replace('.tpl.php', '', $currentRelativePath),
  103. 'fullPath' => $fullPath
  104. ];
  105. }
  106. }
  107. }
  108. /**
  109. * 从模板生成文件
  110. */
  111. private function generateFileFromTemplate(array $templateInfo, string $basePath, array $variables, SymfonyStyle $io): bool
  112. {
  113. $templatePath = $templateInfo['fullPath'];
  114. $targetPath = $basePath . '/' . $templateInfo['target'];
  115. // 确保目标目录存在
  116. $targetDir = dirname($targetPath);
  117. if (!is_dir($targetDir)) {
  118. if (!mkdir($targetDir, 0755, true)) {
  119. $io->error("无法创建目录: $targetDir");
  120. return false;
  121. }
  122. }
  123. // 如果文件已存在,询问是否覆盖
  124. if (file_exists($targetPath)) {
  125. if (!$io->confirm("文件已存在: {$templateInfo['target']},是否覆盖?", false)) {
  126. $io->text("跳过文件: {$templateInfo['target']}");
  127. return true;
  128. }
  129. }
  130. // 提取变量到当前作用域
  131. // 命名空间双斜杆需要改为单斜杠
  132. $namespace = str_replace('\\\\', '\\', $variables['namespace']);
  133. $name = $variables['name'];
  134. $id = $variables['id'];
  135. $description = $variables['description'];
  136. // 生成文件内容
  137. ob_start();
  138. include $templatePath;
  139. $content = ob_get_clean();
  140. // 写入文件
  141. if (file_put_contents($targetPath, $content) !== false) {
  142. $io->text("✓ 生成文件: {$templateInfo['target']}");
  143. return true;
  144. } else {
  145. $io->error("✗ 无法写入文件: {$templateInfo['target']}");
  146. return false;
  147. }
  148. }
  149. /**
  150. * 获取模板文件列表(用于预览)
  151. */
  152. public function getTemplateList(): array
  153. {
  154. return $this->getTemplateFiles();
  155. }
  156. /**
  157. * 检查模板是否在排除列表中
  158. */
  159. private function isExcludedTemplate(string $templatePath): bool
  160. {
  161. foreach ($this->excludedTemplates as $excludedPattern) {
  162. // 支持精确匹配和前缀匹配
  163. if ($templatePath === $excludedPattern || str_starts_with($templatePath, $excludedPattern)) {
  164. return true;
  165. }
  166. }
  167. return false;
  168. }
  169. /**
  170. * 设置排除的模板列表
  171. */
  172. public function setExcludedTemplates(array $excludedTemplates): self
  173. {
  174. $this->excludedTemplates = $excludedTemplates;
  175. return $this;
  176. }
  177. /**
  178. * 添加排除的模板
  179. */
  180. public function addExcludedTemplate(string $templatePath): self
  181. {
  182. if (!in_array($templatePath, $this->excludedTemplates)) {
  183. $this->excludedTemplates[] = $templatePath;
  184. }
  185. return $this;
  186. }
  187. /**
  188. * 移除排除的模板
  189. */
  190. public function removeExcludedTemplate(string $templatePath): self
  191. {
  192. $this->excludedTemplates = array_filter(
  193. $this->excludedTemplates,
  194. fn($excluded) => $excluded !== $templatePath
  195. );
  196. return $this;
  197. }
  198. /**
  199. * 获取当前排除的模板列表
  200. */
  201. public function getExcludedTemplates(): array
  202. {
  203. return $this->excludedTemplates;
  204. }
  205. }