Watcher.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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\Watcher;
  12. use Hyperf\Codec\Json;
  13. use Hyperf\Contract\ConfigInterface;
  14. use Hyperf\Coroutine\Coroutine;
  15. use Hyperf\Engine\Channel;
  16. use Hyperf\Support\Exception\InvalidArgumentException;
  17. use Hyperf\Support\Filesystem\FileNotFoundException;
  18. use Hyperf\Support\Filesystem\Filesystem;
  19. use Hyperf\Watcher\Driver\DriverInterface;
  20. use Hyperf\Watcher\Event\BeforeServerRestart;
  21. use PhpParser\PrettyPrinter\Standard;
  22. use Psr\Container\ContainerInterface;
  23. use Psr\EventDispatcher\EventDispatcherInterface;
  24. use Symfony\Component\Console\Output\OutputInterface;
  25. use Throwable;
  26. use function Hyperf\Support\make;
  27. class Watcher
  28. {
  29. protected DriverInterface $driver;
  30. protected Filesystem $filesystem;
  31. protected array $autoload;
  32. protected ConfigInterface $config;
  33. protected Standard $printer;
  34. protected Channel $channel;
  35. protected string $path = BASE_PATH . '/runtime/container/collectors.cache';
  36. public function __construct(protected ContainerInterface $container, protected Option $option, protected OutputInterface $output)
  37. {
  38. $this->driver = $this->getDriver();
  39. $this->filesystem = new Filesystem();
  40. $json = Json::decode($this->filesystem->get(BASE_PATH . '/composer.json'));
  41. $this->autoload = array_flip($json['autoload']['psr-4'] ?? []);
  42. $this->config = $container->get(ConfigInterface::class);
  43. $this->printer = new Standard();
  44. $this->channel = new Channel(1);
  45. $this->channel->push(true);
  46. }
  47. public function run()
  48. {
  49. $this->dumpAutoload();
  50. $this->restart(true);
  51. $channel = new Channel(999);
  52. Coroutine::create(function () use ($channel) {
  53. $this->driver->watch($channel);
  54. });
  55. $result = [];
  56. while (true) {
  57. $file = $channel->pop(0.001);
  58. if ($file === false) {
  59. if (count($result) > 0) {
  60. $result = [];
  61. $this->restart(false);
  62. }
  63. } else {
  64. $ret = exec(sprintf('%s %s/vendor/hyperf/watcher/collector-reload.php %s', $this->option->getBin(), BASE_PATH, $file));
  65. if (isset($ret['code']) && $ret['code'] === 0) {
  66. $this->output->writeln('Class reload success.');
  67. } else {
  68. $this->output->writeln('Class reload failed.');
  69. $this->output->writeln($ret['output'] ?? '');
  70. }
  71. $result[] = $file;
  72. }
  73. }
  74. }
  75. public function dumpAutoload()
  76. {
  77. $ret = exec('composer dump-autoload -o --no-scripts -d ' . BASE_PATH);
  78. $this->output->writeln($ret['output'] ?? '');
  79. }
  80. public function restart($isStart = true)
  81. {
  82. if (! $this->option->isRestart()) {
  83. return;
  84. }
  85. $file = $this->config->get('server.settings.pid_file');
  86. if (empty($file)) {
  87. throw new FileNotFoundException('The config of pid_file is not found.');
  88. }
  89. $daemonize = $this->config->get('server.settings.daemonize', false);
  90. if ($daemonize) {
  91. throw new InvalidArgumentException('Please set `server.settings.daemonize` to false');
  92. }
  93. if (! $isStart && $this->filesystem->exists($file)) {
  94. $pid = $this->filesystem->get($file);
  95. try {
  96. $this->output->writeln('Stop server...');
  97. $this->container->get(EventDispatcherInterface::class)
  98. ->dispatch(new BeforeServerRestart($pid));
  99. if (posix_kill((int) $pid, 0)) {
  100. posix_kill((int) $pid, SIGTERM);
  101. }
  102. } catch (Throwable) {
  103. $this->output->writeln('Stop server failed. Please execute `composer dump-autoload -o`');
  104. }
  105. }
  106. Coroutine::create(function () {
  107. $this->channel->pop();
  108. $this->output->writeln('Start server ...');
  109. $descriptorSpec = [
  110. 0 => STDIN,
  111. 1 => STDOUT,
  112. 2 => STDERR,
  113. ];
  114. proc_open(
  115. command: $this->option->getBin() . ' ' . BASE_PATH . '/' . $this->option->getCommand(),
  116. descriptor_spec: $descriptorSpec,
  117. pipes: $pipes
  118. );
  119. $this->output->writeln('Stop server success.');
  120. $this->channel->push(1);
  121. });
  122. }
  123. protected function getDriver()
  124. {
  125. $driver = $this->option->getDriver();
  126. if (! class_exists($driver)) {
  127. throw new \InvalidArgumentException('Driver not support.');
  128. }
  129. return make($driver, ['option' => $this->option]);
  130. }
  131. }