watch.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. #!/usr/bin/env php
  2. <?php
  3. /**
  4. * Hyperf Watch Hot Reload Scripts
  5. * From: https://github.com/ha-ni-cc/hyperf-watch
  6. * Author: hanicc@qq.com
  7. * Usage:
  8. * Open the terminal console in the project root directory and enter:php watch
  9. * 在项目根目录下打开终端控制台,输入:php watch
  10. * If you want to clean the /runtime/container cache, enter: php watch -c
  11. * 如果你想要清除/runtime/container缓存,则输入:php watch -c
  12. */
  13. # PHP Bin File PHP程序所在路径(默认自动获取)
  14. const PHP_BIN_FILE = 'which php';
  15. # Watch Dir 监听目录(默认监听脚本所在的根目录)
  16. const WATCH_DIR = __DIR__ . '/';
  17. # Watch Ext 监听扩展名(多个可用英文逗号隔开)
  18. const WATCH_EXT = 'php,env';
  19. # Exclude Dir 排除目录(不监听的目录,数组形式)
  20. const EXCLUDE_DIR = ['vendor', 'runtime', 'public'];
  21. # Entry Point File 入口文件
  22. const ENTRY_POINT_FILE = __DIR__ . '/bin/hyperf.php';
  23. # Start Command 启动命令
  24. const START_COMMAND = [ENTRY_POINT_FILE, 'start'];
  25. # PID File Path PID文件路径
  26. const PID_FILE_PATH = __DIR__ . '/runtime/hyperf.pid';
  27. # Scan Interval 扫描间隔(毫秒,默认2000)
  28. const SCAN_INTERVAL = 2000;
  29. # Console Color 控制台颜色
  30. const CONSOLE_COLOR_DEFAULT = "\033[0m";
  31. const CONSOLE_COLOR_RED = "\033[0;31m";
  32. const CONSOLE_COLOR_GREEN = "\033[0;32m";
  33. const CONSOLE_COLOR_YELLOW = "\033[0;33m";
  34. const CONSOLE_COLOR_BLUE = "\033[0;34m";
  35. if (!function_exists('exec')) {
  36. echo '[x] 请在php.ini配置中取消禁用exec方法' . PHP_EOL;
  37. exit(1);
  38. }
  39. define('PHP', PHP_BIN_FILE == 'which php' ? exec('which php') : PHP_BIN_FILE);
  40. if (!file_exists(PHP) || !is_executable(PHP)) {
  41. echo '[x] PHP bin (" ' . PHP . ' ") 路径没有找到或无法执行,请确认路径正确?' . PHP_EOL;
  42. exit(1);
  43. }
  44. if (!file_exists(ENTRY_POINT_FILE)) {
  45. echo '[x] 入口文件 ("' . ENTRY_POINT_FILE . '") 没有找到,请确认文件存在?' . PHP_EOL;
  46. exit(1);
  47. }
  48. # 加载env
  49. $content = @file_get_contents('.env');
  50. $values = array_filter(preg_split("/(\r\n|\n|\r)/", $content));
  51. foreach ($values as $val) {
  52. if (substr($val, 0, 1) === '#') {
  53. continue;
  54. }
  55. list($name, $value) = explode('=', $val);
  56. $_ENV[$name] = $value;
  57. }
  58. use Swoole\Process;
  59. use Swoole\Timer;
  60. use Swoole\Event;
  61. swoole_async_set(['enable_coroutine' => false, 'log_level' => SWOOLE_LOG_INFO]);
  62. $hashes = [];
  63. $serve = null;
  64. echo CONSOLE_COLOR_YELLOW . "🚀 Start @ " . date('Y-m-d H:i:s') . PHP_EOL;
  65. start();
  66. state();
  67. Timer::tick(SCAN_INTERVAL, 'watch');
  68. function killOldProcess()
  69. {
  70. // pid存在则关闭存在的进程
  71. if (file_exists(PID_FILE_PATH) && $pid = @file_get_contents(PID_FILE_PATH)) {
  72. if (!@posix_kill($pid)) forceKill();
  73. } else forceKill();
  74. }
  75. function forceKill($match = '')
  76. {
  77. if (!$match) {
  78. $match = @$_ENV['APP_NAME'] . '.Master';
  79. }
  80. // 适配MacOS
  81. if (PHP_OS == 'Darwin') $match = ENTRY_POINT_FILE;
  82. $command = "ps -ef | grep '$match' | grep -v grep | awk '{print $2}' | xargs kill -9 2>&1";
  83. // 找不到pid,强杀进程
  84. exec($command);
  85. }
  86. function start()
  87. {
  88. // 杀旧进程
  89. killOldProcess();
  90. global $serve;
  91. $serve = new Process('serve', true);
  92. $serve->start();
  93. if (false === $serve->pid) {
  94. echo swoole_strerror(swoole_errno()) . PHP_EOL;
  95. exit(1);
  96. }
  97. addEvent($serve);
  98. }
  99. function addEvent($serve)
  100. {
  101. Event::add($serve->pipe, function () use (&$serve) {
  102. $message = @$serve->read();
  103. if (!empty($message)) {
  104. $debug = strpos($message, '[DEBUG]') !== false;
  105. $info = strpos($message, '[INFO]') !== false;
  106. $warn = strpos($message, '[WARNING]') !== false;
  107. $error = strpos($message, '[ERROR]') !== false;
  108. if ($debug) {
  109. echo CONSOLE_COLOR_BLUE . $message;
  110. } elseif ($info) {
  111. echo CONSOLE_COLOR_GREEN . $message;
  112. } elseif ($warn) {
  113. echo CONSOLE_COLOR_YELLOW . $message;
  114. } elseif ($error) {
  115. echo CONSOLE_COLOR_RED . $message;
  116. } else echo CONSOLE_COLOR_DEFAULT . $message;
  117. echo CONSOLE_COLOR_DEFAULT;
  118. }
  119. });
  120. }
  121. function watch()
  122. {
  123. global $hashes;
  124. foreach ($hashes as $pathName => $currentHash) {
  125. if (!file_exists($pathName)) {
  126. unset($hashes[$pathName]);
  127. continue;
  128. }
  129. $newHash = fileHash($pathName);
  130. if ($newHash != $currentHash) {
  131. change();
  132. state();
  133. break;
  134. }
  135. }
  136. }
  137. function state()
  138. {
  139. global $hashes;
  140. $files = phpFiles(WATCH_DIR);
  141. $hashes = array_combine($files, array_map('fileHash', $files));
  142. $count = count($hashes);
  143. echo CONSOLE_COLOR_YELLOW . "📡 Watching $count files..." . PHP_EOL;
  144. }
  145. function change()
  146. {
  147. global $serve;
  148. echo CONSOLE_COLOR_YELLOW . "🔄 Restart @ " . date('Y-m-d H:i:s') . PHP_EOL;
  149. Process::kill($serve->pid);
  150. start();
  151. }
  152. function serve(Process $serve)
  153. {
  154. $opt = getopt('c');
  155. # if (isset($opt['c'])) echo exec(PHP . ' ' . ENTRY_POINT_FILE . ' di:init-proxy') . '..' . PHP_EOL;
  156. if (isset($opt['c'])) delDir('./runtime/container');
  157. $serve->exec(PHP, START_COMMAND);
  158. }
  159. function fileHash(string $pathname): string
  160. {
  161. $contents = file_get_contents($pathname);
  162. if (false === $contents) {
  163. return 'deleted';
  164. }
  165. return md5($contents);
  166. }
  167. function phpFiles(string $dirname): array
  168. {
  169. $directory = new RecursiveDirectoryIterator($dirname);
  170. $filter = new Filter($directory);
  171. $iterator = new RecursiveIteratorIterator($filter);
  172. return array_map(function ($fileInfo) {
  173. return $fileInfo->getPathname();
  174. }, iterator_to_array($iterator));
  175. }
  176. function delDir($path)
  177. {
  178. if (is_dir($path)) {
  179. //扫描一个目录内的所有目录和文件并返回数组
  180. $dirs = scandir($path);
  181. foreach ($dirs as $dir) {
  182. //排除目录中的当前目录(.)和上一级目录(..)
  183. if ($dir != '.' && $dir != '..') {
  184. //如果是目录则递归子目录,继续操作
  185. $sonDir = $path . '/' . $dir;
  186. if (is_dir($sonDir)) {
  187. //递归删除
  188. delDir($sonDir);
  189. //目录内的子目录和文件删除后删除空目录
  190. @rmdir($sonDir);
  191. } else {
  192. //如果是文件直接删除
  193. @unlink($sonDir);
  194. }
  195. }
  196. }
  197. @rmdir($path);
  198. }
  199. }
  200. class Filter extends RecursiveFilterIterator
  201. {
  202. public function accept()
  203. {
  204. if ($this->current()->isDir()) {
  205. if (preg_match('/^\./', $this->current()->getFilename())) {
  206. return false;
  207. }
  208. return !in_array($this->current()->getFilename(), EXCLUDE_DIR);
  209. }
  210. $list = array_map(function (string $item): string {
  211. return "\.$item";
  212. }, explode(',', WATCH_EXT));
  213. $list = implode('|', $list);
  214. return preg_match("/($list)$/", $this->current()->getFilename());
  215. }
  216. }