Sin descripción
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: liu21st <liu21st@gmail.com>
  10. // +----------------------------------------------------------------------
  11. namespace think;
  12. class Build
  13. {
  14. /**
  15. * 应用对象
  16. * @var App
  17. */
  18. protected $app;
  19. /**
  20. * 应用目录
  21. * @var string
  22. */
  23. protected $basePath;
  24. public function __construct(App $app)
  25. {
  26. $this->app = $app;
  27. $this->basePath = $this->app->getAppPath();
  28. }
  29. /**
  30. * 根据传入的build资料创建目录和文件
  31. * @access public
  32. * @param array $build build列表
  33. * @param string $namespace 应用类库命名空间
  34. * @param bool $suffix 类库后缀
  35. * @return void
  36. */
  37. public function run(array $build = [], $namespace = 'app', $suffix = false)
  38. {
  39. // 锁定
  40. $lockfile = $this->basePath . 'build.lock';
  41. if (is_writable($lockfile)) {
  42. return;
  43. } elseif (!touch($lockfile)) {
  44. throw new Exception('应用目录[' . $this->basePath . ']不可写,目录无法自动生成!<BR>请手动生成项目目录~', 10006);
  45. }
  46. foreach ($build as $module => $list) {
  47. if ('__dir__' == $module) {
  48. // 创建目录列表
  49. $this->buildDir($list);
  50. } elseif ('__file__' == $module) {
  51. // 创建文件列表
  52. $this->buildFile($list);
  53. } else {
  54. // 创建模块
  55. $this->module($module, $list, $namespace, $suffix);
  56. }
  57. }
  58. // 解除锁定
  59. unlink($lockfile);
  60. }
  61. /**
  62. * 创建目录
  63. * @access protected
  64. * @param array $list 目录列表
  65. * @return void
  66. */
  67. protected function buildDir($list)
  68. {
  69. foreach ($list as $dir) {
  70. $this->checkDirBuild($this->basePath . $dir);
  71. }
  72. }
  73. /**
  74. * 创建文件
  75. * @access protected
  76. * @param array $list 文件列表
  77. * @return void
  78. */
  79. protected function buildFile($list)
  80. {
  81. foreach ($list as $file) {
  82. if (!is_dir($this->basePath . dirname($file))) {
  83. // 创建目录
  84. mkdir($this->basePath . dirname($file), 0755, true);
  85. }
  86. if (!is_file($this->basePath . $file)) {
  87. file_put_contents($this->basePath . $file, 'php' == pathinfo($file, PATHINFO_EXTENSION) ? "<?php\n" : '');
  88. }
  89. }
  90. }
  91. /**
  92. * 创建模块
  93. * @access public
  94. * @param string $module 模块名
  95. * @param array $list build列表
  96. * @param string $namespace 应用类库命名空间
  97. * @param bool $suffix 类库后缀
  98. * @return void
  99. */
  100. public function module($module = '', $list = [], $namespace = 'app', $suffix = false)
  101. {
  102. $module = $module ? $module : '';
  103. if (!is_dir($this->basePath . $module)) {
  104. // 创建模块目录
  105. mkdir($this->basePath . $module);
  106. }
  107. if (basename($this->app->getRuntimePath()) != $module) {
  108. // 创建配置文件和公共文件
  109. $this->buildCommon($module);
  110. // 创建模块的默认页面
  111. $this->buildHello($module, $namespace, $suffix);
  112. }
  113. if (empty($list)) {
  114. // 创建默认的模块目录和文件
  115. $list = [
  116. '__file__' => ['common.php'],
  117. '__dir__' => ['controller', 'model', 'view', 'config'],
  118. ];
  119. }
  120. // 创建子目录和文件
  121. foreach ($list as $path => $file) {
  122. $modulePath = $this->basePath . $module . DIRECTORY_SEPARATOR;
  123. if ('__dir__' == $path) {
  124. // 生成子目录
  125. foreach ($file as $dir) {
  126. $this->checkDirBuild($modulePath . $dir);
  127. }
  128. } elseif ('__file__' == $path) {
  129. // 生成(空白)文件
  130. foreach ($file as $name) {
  131. if (!is_file($modulePath . $name)) {
  132. file_put_contents($modulePath . $name, 'php' == pathinfo($name, PATHINFO_EXTENSION) ? "<?php\n" : '');
  133. }
  134. }
  135. } else {
  136. // 生成相关MVC文件
  137. foreach ($file as $val) {
  138. $val = trim($val);
  139. $filename = $modulePath . $path . DIRECTORY_SEPARATOR . $val . ($suffix ? ucfirst($path) : '') . '.php';
  140. $space = $namespace . '\\' . ($module ? $module . '\\' : '') . $path;
  141. $class = $val . ($suffix ? ucfirst($path) : '');
  142. switch ($path) {
  143. case 'controller': // 控制器
  144. $content = "<?php\nnamespace {$space};\n\nclass {$class}\n{\n\n}";
  145. break;
  146. case 'model': // 模型
  147. $content = "<?php\nnamespace {$space};\n\nuse think\Model;\n\nclass {$class} extends Model\n{\n\n}";
  148. break;
  149. case 'view': // 视图
  150. $filename = $modulePath . $path . DIRECTORY_SEPARATOR . $val . '.html';
  151. $this->checkDirBuild(dirname($filename));
  152. $content = '';
  153. break;
  154. default:
  155. // 其他文件
  156. $content = "<?php\nnamespace {$space};\n\nclass {$class}\n{\n\n}";
  157. }
  158. if (!is_file($filename)) {
  159. file_put_contents($filename, $content);
  160. }
  161. }
  162. }
  163. }
  164. }
  165. /**
  166. * 根据注释自动生成路由规则
  167. * @access public
  168. * @param bool $suffix 类库后缀
  169. * @param string $layer 控制器层目录名
  170. * @return string
  171. */
  172. public function buildRoute($suffix = false, $layer = '')
  173. {
  174. $namespace = $this->app->getNameSpace();
  175. $content = '<?php ' . PHP_EOL . '//根据 Annotation 自动生成的路由规则';
  176. if (!$layer) {
  177. $layer = $this->app->config('app.url_controller_layer');
  178. }
  179. if ($this->app->config('app.app_multi_module')) {
  180. $modules = glob($this->basePath . '*', GLOB_ONLYDIR);
  181. foreach ($modules as $module) {
  182. $module = basename($module);
  183. if (in_array($module, $this->app->config('app.deny_module_list'))) {
  184. continue;
  185. }
  186. $path = $this->basePath . $module . DIRECTORY_SEPARATOR . $layer . DIRECTORY_SEPARATOR;
  187. $content .= $this->buildDirRoute($path, $namespace, $module, $suffix, $layer);
  188. }
  189. } else {
  190. $path = $this->basePath . $layer . DIRECTORY_SEPARATOR;
  191. $content .= $this->buildDirRoute($path, $namespace, '', $suffix, $layer);
  192. }
  193. $filename = $this->app->getRuntimePath() . 'build_route.php';
  194. file_put_contents($filename, $content);
  195. return $filename;
  196. }
  197. /**
  198. * 生成子目录控制器类的路由规则
  199. * @access protected
  200. * @param string $path 控制器目录
  201. * @param string $namespace 应用命名空间
  202. * @param string $module 模块
  203. * @param bool $suffix 类库后缀
  204. * @param string $layer 控制器层目录名
  205. * @return string
  206. */
  207. protected function buildDirRoute($path, $namespace, $module, $suffix, $layer)
  208. {
  209. $content = '';
  210. $controllers = glob($path . '*.php');
  211. foreach ($controllers as $controller) {
  212. $controller = basename($controller, '.php');
  213. $class = new \ReflectionClass($namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $controller);
  214. if (strpos($layer, '\\')) {
  215. // 多级控制器
  216. $level = str_replace(DIRECTORY_SEPARATOR, '.', substr($layer, 11));
  217. $controller = $level . '.' . $controller;
  218. $length = strlen(strstr($layer, '\\', true));
  219. } else {
  220. $length = strlen($layer);
  221. }
  222. if ($suffix) {
  223. $controller = substr($controller, 0, -$length);
  224. }
  225. $content .= $this->getControllerRoute($class, $module, $controller);
  226. }
  227. $subDir = glob($path . '*', GLOB_ONLYDIR);
  228. foreach ($subDir as $dir) {
  229. $content .= $this->buildDirRoute($dir . DIRECTORY_SEPARATOR, $namespace, $module, $suffix, $layer . '\\' . basename($dir));
  230. }
  231. return $content;
  232. }
  233. /**
  234. * 生成控制器类的路由规则
  235. * @access protected
  236. * @param string $class 控制器完整类名
  237. * @param string $module 模块名
  238. * @param string $controller 控制器名
  239. * @return string
  240. */
  241. protected function getControllerRoute($class, $module, $controller)
  242. {
  243. $content = '';
  244. $comment = $class->getDocComment();
  245. if (false !== strpos($comment, '@route(')) {
  246. $comment = $this->parseRouteComment($comment);
  247. $route = ($module ? $module . '/' : '') . $controller;
  248. $comment = preg_replace('/route\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::resource(\1,\'' . $route . '\')', $comment);
  249. $content .= PHP_EOL . $comment;
  250. } elseif (false !== strpos($comment, '@alias(')) {
  251. $comment = $this->parseRouteComment($comment, '@alias(');
  252. $route = ($module ? $module . '/' : '') . $controller;
  253. $comment = preg_replace('/alias\(\s?([\'\"][\-\_\/\w]+[\'\"])\s?\)/is', 'Route::alias(\1,\'' . $route . '\')', $comment);
  254. $content .= PHP_EOL . $comment;
  255. }
  256. $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);
  257. foreach ($methods as $method) {
  258. $comment = $this->getMethodRouteComment($module, $controller, $method);
  259. if ($comment) {
  260. $content .= PHP_EOL . $comment;
  261. }
  262. }
  263. return $content;
  264. }
  265. /**
  266. * 解析路由注释
  267. * @access protected
  268. * @param string $comment
  269. * @param string $tag
  270. * @return string
  271. */
  272. protected function parseRouteComment($comment, $tag = '@route(')
  273. {
  274. $comment = substr($comment, 3, -2);
  275. $comment = explode(PHP_EOL, substr(strstr(trim($comment), $tag), 1));
  276. $comment = array_map(function ($item) {return trim(trim($item), ' \t*');}, $comment);
  277. if (count($comment) > 1) {
  278. $key = array_search('', $comment);
  279. $comment = array_slice($comment, 0, false === $key ? 1 : $key);
  280. }
  281. $comment = implode(PHP_EOL . "\t", $comment) . ';';
  282. if (strpos($comment, '{')) {
  283. $comment = preg_replace_callback('/\{\s?.*?\s?\}/s', function ($matches) {
  284. return false !== strpos($matches[0], '"') ? '[' . substr(var_export(json_decode($matches[0], true), true), 7, -1) . ']' : $matches[0];
  285. }, $comment);
  286. }
  287. return $comment;
  288. }
  289. /**
  290. * 获取方法的路由注释
  291. * @access protected
  292. * @param string $module 模块
  293. * @param string $controller 控制器名
  294. * @param \ReflectMethod $reflectMethod
  295. * @return string|void
  296. */
  297. protected function getMethodRouteComment($module, $controller, $reflectMethod)
  298. {
  299. $comment = $reflectMethod->getDocComment();
  300. if (false !== strpos($comment, '@route(')) {
  301. $comment = $this->parseRouteComment($comment);
  302. $action = $reflectMethod->getName();
  303. if ($suffix = $this->app->config('app.action_suffix')) {
  304. $action = substr($action, 0, -strlen($suffix));
  305. }
  306. $route = ($module ? $module . '/' : '') . $controller . '/' . $action;
  307. $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\,?\s?[\'\"]?(\w+?)[\'\"]?\s?\)/is', 'Route::\2(\1,\'' . $route . '\')', $comment);
  308. $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::rule(\1,\'' . $route . '\')', $comment);
  309. return $comment;
  310. }
  311. }
  312. /**
  313. * 创建模块的欢迎页面
  314. * @access protected
  315. * @param string $module 模块名
  316. * @param string $namespace 应用类库命名空间
  317. * @param bool $suffix 类库后缀
  318. * @return void
  319. */
  320. protected function buildHello($module, $namespace, $suffix = false)
  321. {
  322. $filename = $this->basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . ($suffix ? 'Controller' : '') . '.php';
  323. if (!is_file($filename)) {
  324. $content = file_get_contents($this->app->getThinkPath() . 'tpl' . DIRECTORY_SEPARATOR . 'default_index.tpl');
  325. $content = str_replace(['{$app}', '{$module}', '{layer}', '{$suffix}'], [$namespace, $module ? $module . '\\' : '', 'controller', $suffix ? 'Controller' : ''], $content);
  326. $this->checkDirBuild(dirname($filename));
  327. file_put_contents($filename, $content);
  328. }
  329. }
  330. /**
  331. * 创建模块的公共文件
  332. * @access protected
  333. * @param string $module 模块名
  334. * @return void
  335. */
  336. protected function buildCommon($module)
  337. {
  338. $filename = $this->app->getConfigPath() . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'app.php';
  339. $this->checkDirBuild(dirname($filename));
  340. if (!is_file($filename)) {
  341. file_put_contents($filename, "<?php\n//配置文件\nreturn [\n\n];");
  342. }
  343. $filename = $this->basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'common.php';
  344. if (!is_file($filename)) {
  345. file_put_contents($filename, "<?php\n");
  346. }
  347. }
  348. /**
  349. * 创建目录
  350. * @access protected
  351. * @param string $dirname 目录名称
  352. * @return void
  353. */
  354. protected function checkDirBuild($dirname)
  355. {
  356. if (!is_dir($dirname)) {
  357. mkdir($dirname, 0755, true);
  358. }
  359. }
  360. }