CodeEngine.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | saiadmin [ saiadmin快速开发框架 ]
  4. // +----------------------------------------------------------------------
  5. // | Author: sai <1430792918@qq.com>
  6. // +----------------------------------------------------------------------
  7. namespace plugin\saiadmin\utils\code;
  8. use Twig\TwigFilter;
  9. use Twig\Environment;
  10. use Twig\Loader\FilesystemLoader;
  11. use plugin\saiadmin\exception\ApiException;
  12. /**
  13. * 代码生成引擎
  14. */
  15. class CodeEngine
  16. {
  17. /**
  18. * @var array 值栈
  19. */
  20. private array $value = [];
  21. /**
  22. * 模板名称
  23. * @var string
  24. */
  25. private string $stub = 'saiadmin';
  26. /**
  27. * 获取配置文件
  28. * @return string[]
  29. */
  30. private static function _getConfig(): array
  31. {
  32. return [
  33. 'template_path' => base_path().DIRECTORY_SEPARATOR.'plugin'.DIRECTORY_SEPARATOR.'saiadmin'.DIRECTORY_SEPARATOR.'utils'.DIRECTORY_SEPARATOR.'code'.DIRECTORY_SEPARATOR.'stub',
  34. 'generate_path' => runtime_path().DIRECTORY_SEPARATOR.'code_engine'.DIRECTORY_SEPARATOR.'saiadmin',
  35. ];
  36. }
  37. /**
  38. * 初始化
  39. * @param array $data 数据
  40. */
  41. public function __construct(array $data)
  42. {
  43. // 读取配置文件
  44. $config = self::_getConfig();
  45. // 判断模板是否存在
  46. if (!is_dir($config['template_path'])) {
  47. throw new ApiException('模板目录不存在!');
  48. }
  49. // 判断文件生成目录是否存在
  50. if (!is_dir($config['generate_path'])) {
  51. mkdir($config['generate_path'], 0770, true);
  52. }
  53. // 赋值
  54. $this->value = $data;
  55. }
  56. /**
  57. * 设置模板名称
  58. * @param $stub
  59. * @return void
  60. */
  61. public function setStub($stub): void
  62. {
  63. $this->stub = $stub;
  64. }
  65. /**
  66. * 渲染文件内容
  67. */
  68. public function renderContent($path, $filename): string
  69. {
  70. $config = self::_getConfig();
  71. $path = $config['template_path'].DIRECTORY_SEPARATOR.$this->stub.DIRECTORY_SEPARATOR.$path;
  72. $loader = new FilesystemLoader($path);
  73. $twig = new Environment($loader);
  74. $camelFilter = new TwigFilter('camel', function ($value) {
  75. static $cache = [];
  76. $key = $value;
  77. if (isset($cache[$key])) {
  78. return $cache[$key];
  79. }
  80. $value = ucwords(str_replace(['-', '_'], ' ', $value));
  81. return $cache[$key] = str_replace(' ', '', $value);
  82. });
  83. $boolFilter = new TwigFilter('bool', function ($value) {
  84. if ($value == 1) {
  85. return 'true';
  86. } else {
  87. return 'false';
  88. }
  89. });
  90. $formatFilter = new TwigFilter('formatNumber', function ($value) {
  91. if (ctype_digit((string)$value)) {
  92. return $value;
  93. } else {
  94. return '1';
  95. }
  96. });
  97. $defaultFilter = new TwigFilter('parseNumber', function ($value) {
  98. if ($value) {
  99. return $value;
  100. } else {
  101. return 'null';
  102. }
  103. });
  104. $containsFilter = new TwigFilter('str_contains', function ($haystack, $needle) {
  105. return str_contains($haystack ?? '', $needle ?? '');
  106. });
  107. $twig->addFilter($camelFilter);
  108. $twig->addFilter($boolFilter);
  109. $twig->addFilter($containsFilter);
  110. $twig->addFilter($formatFilter);
  111. $twig->addFilter($defaultFilter);
  112. return $twig->render($filename, $this->value);
  113. }
  114. /**
  115. * 生成后端文件
  116. */
  117. public function generateBackend($action, $content): void
  118. {
  119. $outPath = '';
  120. if ($this->value['template'] == 'app') {
  121. $rootPath = base_path() . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . $this->value['namespace'];
  122. } else {
  123. $rootPath = base_path() . DIRECTORY_SEPARATOR . 'plugin' . DIRECTORY_SEPARATOR . $this->value['namespace'] . DIRECTORY_SEPARATOR . 'app';
  124. }
  125. $subPath = '';
  126. if (!empty($this->value['package_name'])) {
  127. $subPath = DIRECTORY_SEPARATOR . $this->value['package_name'];
  128. }
  129. switch ($action) {
  130. case 'controller':
  131. $outPath = $rootPath . DIRECTORY_SEPARATOR . 'controller' . $subPath . DIRECTORY_SEPARATOR . $this->value['class_name'] . 'Controller.php';
  132. break;
  133. case 'logic':
  134. $outPath = $rootPath . DIRECTORY_SEPARATOR . 'logic' . $subPath . DIRECTORY_SEPARATOR . $this->value['class_name'] . 'Logic.php';
  135. break;
  136. case 'model':
  137. $outPath = $rootPath . DIRECTORY_SEPARATOR . 'model' . $subPath . DIRECTORY_SEPARATOR . $this->value['class_name'] . '.php';
  138. break;
  139. case 'validate':
  140. $outPath = $rootPath . DIRECTORY_SEPARATOR . 'validate' . $subPath . DIRECTORY_SEPARATOR . $this->value['class_name'] . 'Validate.php';
  141. break;
  142. default:
  143. break;
  144. }
  145. if (empty($outPath)) {
  146. throw new ApiException('文件类型异常,无法生成指定文件!');
  147. }
  148. if (!is_dir(dirname($outPath))) {
  149. mkdir(dirname($outPath), 0777, true);
  150. }
  151. file_put_contents($outPath, $content);
  152. }
  153. /**
  154. * 生成前端文件
  155. */
  156. public function generateFrontend($action, $content): void
  157. {
  158. $rootPath = dirname(base_path()) . DIRECTORY_SEPARATOR . $this->value['generate_path'];
  159. if (!is_dir($rootPath)) {
  160. throw new ApiException('前端目录查找失败,必须与后端目录为同级目录!');
  161. }
  162. $rootPath = $rootPath . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . $this->value['namespace'];
  163. $subPath = '';
  164. if (!empty($this->value['package_name'])) {
  165. $subPath = DIRECTORY_SEPARATOR . $this->value['package_name'];
  166. }
  167. switch ($action) {
  168. case 'index':
  169. $outPath = $rootPath . $subPath . DIRECTORY_SEPARATOR . $this->value['business_name'] . DIRECTORY_SEPARATOR . 'index.vue';
  170. break;
  171. case 'edit':
  172. $outPath = $rootPath . $subPath . DIRECTORY_SEPARATOR . $this->value['business_name'] . DIRECTORY_SEPARATOR . 'edit.vue';
  173. break;
  174. case 'api':
  175. $outPath = $rootPath . DIRECTORY_SEPARATOR . 'api' . $subPath . DIRECTORY_SEPARATOR . $this->value['business_name'] . '.js';
  176. break;
  177. default:
  178. break;
  179. }
  180. if (empty($outPath)) {
  181. throw new ApiException('文件类型异常,无法生成指定文件!');
  182. }
  183. if (!is_dir(dirname($outPath))) {
  184. mkdir(dirname($outPath), 0777, true);
  185. }
  186. file_put_contents($outPath, $content);
  187. }
  188. /**
  189. * 生成临时文件
  190. */
  191. public function generateTemp(): void
  192. {
  193. $config = self::_getConfig();
  194. $rootPath = $config['generate_path'];
  195. $vuePath = $rootPath . DIRECTORY_SEPARATOR . 'vue' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . $this->value['namespace'];
  196. $phpPath = $rootPath . DIRECTORY_SEPARATOR . 'php';
  197. $sqlPath = $rootPath . DIRECTORY_SEPARATOR . 'sql';
  198. if ($this->value['template'] == 'app') {
  199. $phpPath = $phpPath . DIRECTORY_SEPARATOR . 'app' . DIRECTORY_SEPARATOR . $this->value['namespace'];
  200. } else {
  201. $phpPath = $phpPath . DIRECTORY_SEPARATOR . 'plugin' . DIRECTORY_SEPARATOR . $this->value['namespace'] . DIRECTORY_SEPARATOR . 'app';
  202. }
  203. $subPath = '';
  204. if (!empty($this->value['package_name'])) {
  205. $subPath = DIRECTORY_SEPARATOR . $this->value['package_name'];
  206. }
  207. $indexOutPath = $vuePath . $subPath . DIRECTORY_SEPARATOR . $this->value['business_name']. DIRECTORY_SEPARATOR . 'index.vue';
  208. $this->checkPath($indexOutPath);
  209. $indexContent = $this->renderContent('vue', 'index.stub');
  210. file_put_contents($indexOutPath, $indexContent);
  211. $editOutPath = $vuePath . $subPath . DIRECTORY_SEPARATOR . $this->value['business_name']. DIRECTORY_SEPARATOR . 'edit.vue';
  212. $this->checkPath($editOutPath);
  213. $editContent = $this->renderContent('vue', 'edit.stub');
  214. file_put_contents($editOutPath, $editContent);
  215. $viewOutPath = $vuePath . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . $this->value['business_name'] . '.js';
  216. $this->checkPath($viewOutPath);
  217. $viewContent = $this->renderContent('js', 'api.stub');
  218. file_put_contents($viewOutPath, $viewContent);
  219. $controllerOutPath = $phpPath . DIRECTORY_SEPARATOR . 'controller' . $subPath . DIRECTORY_SEPARATOR . $this->value['class_name'] . 'Controller.php';
  220. $this->checkPath($controllerOutPath);
  221. $controllerContent = $this->renderContent('php', 'controller.stub');
  222. file_put_contents($controllerOutPath, $controllerContent);
  223. $logicOutPath = $phpPath . DIRECTORY_SEPARATOR . 'logic' . $subPath . DIRECTORY_SEPARATOR . $this->value['class_name'] . 'Logic.php';
  224. $this->checkPath($logicOutPath);
  225. $logicContent = $this->renderContent('php', 'logic.stub');
  226. file_put_contents($logicOutPath, $logicContent);
  227. $validateOutPath = $phpPath . DIRECTORY_SEPARATOR . 'validate' . $subPath . DIRECTORY_SEPARATOR . $this->value['class_name'] . 'Validate.php';
  228. $this->checkPath($validateOutPath);
  229. $validateContent = $this->renderContent('php', 'validate.stub');
  230. file_put_contents($validateOutPath, $validateContent);
  231. $modelOutPath = $phpPath . DIRECTORY_SEPARATOR . 'model' . $subPath . DIRECTORY_SEPARATOR . $this->value['class_name'] . '.php';
  232. $this->checkPath($modelOutPath);
  233. $modelContent = $this->renderContent('php', 'model.stub');
  234. file_put_contents($modelOutPath, $modelContent);
  235. $sqlOutPath = $sqlPath . DIRECTORY_SEPARATOR . 'sql.sql';
  236. $this->checkPath($sqlOutPath);
  237. $sqlContent = $this->renderContent('sql', 'sql.stub');
  238. file_put_contents($sqlOutPath, $sqlContent);
  239. }
  240. /**
  241. * 检查并生成路径
  242. * @param $path
  243. * @return void
  244. */
  245. protected function checkPath($path): void
  246. {
  247. if (!is_dir(dirname($path))) {
  248. mkdir(dirname($path), 0777, true);
  249. }
  250. }
  251. }