├── .gitattributes ├── collector-reload.php ├── composer.json ├── publish └── watcher.php ├── src ├── Ast │ ├── Metadata.php │ └── RewriteClassNameVisitor.php ├── Command │ └── WatchCommand.php ├── ConfigProvider.php ├── Driver │ ├── AbstractDriver.php │ ├── DriverInterface.php │ ├── FindDriver.php │ ├── FindNewerDriver.php │ ├── FswatchDriver.php │ └── ScanFileDriver.php ├── Event │ └── BeforeServerRestart.php ├── Functions.php ├── Listener │ └── ReloadDotenvListener.php ├── Option.php ├── Process.php └── Watcher.php └── watcher.php /.gitattributes: -------------------------------------------------------------------------------- 1 | /tests export-ignore 2 | /.github export-ignore 3 | -------------------------------------------------------------------------------- /collector-reload.php: -------------------------------------------------------------------------------- 1 | =8.1", 13 | "ext-posix": "*", 14 | "hyperf/codec": "~3.1.0", 15 | "hyperf/command": "~3.1.0", 16 | "hyperf/contract": "~3.1.0", 17 | "hyperf/coordinator": "~3.1.0", 18 | "hyperf/coroutine": "~3.1.0", 19 | "hyperf/di": "~3.1.0", 20 | "hyperf/engine": "~3.1.0", 21 | "hyperf/framework": "~3.1.0", 22 | "hyperf/stringable": "~3.1.0", 23 | "hyperf/support": "~3.1.0" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Hyperf\\Watcher\\": "src/" 28 | }, 29 | "files": [ 30 | "src/Functions.php" 31 | ] 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "HyperfTest\\Watcher\\": "tests/" 36 | } 37 | }, 38 | "config": { 39 | "sort-packages": true 40 | }, 41 | "extra": { 42 | "branch-alias": { 43 | "dev-master": "3.1-dev" 44 | }, 45 | "hyperf": { 46 | "config": "Hyperf\\Watcher\\ConfigProvider" 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /publish/watcher.php: -------------------------------------------------------------------------------- 1 | ScanFileDriver::class, 16 | 'bin' => PHP_BINARY, 17 | 'watch' => [ 18 | 'dir' => ['app', 'config'], 19 | 'file' => ['.env'], 20 | 'scan_interval' => 2000, 21 | ], 22 | 'ext' => ['.php', '.env'], 23 | ]; 24 | -------------------------------------------------------------------------------- /src/Ast/Metadata.php: -------------------------------------------------------------------------------- 1 | className !== null; 26 | } 27 | 28 | public function toClassName(): string 29 | { 30 | return $this->namespace . '\\' . $this->className; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Ast/RewriteClassNameVisitor.php: -------------------------------------------------------------------------------- 1 | metadata->namespace = $node->name->toCodeString(); 29 | return $node; 30 | case ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Enum_) && $node->name: 31 | $className = $node->name->name; 32 | $this->metadata->className = $className; 33 | return $node; 34 | } 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Command/WatchCommand.php: -------------------------------------------------------------------------------- 1 | setDescription('watch command'); 32 | $this->addOption('config', 'C', InputOption::VALUE_OPTIONAL, '', '.watcher.php'); 33 | $this->addOption('file', 'F', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, '', []); 34 | $this->addOption('dir', 'D', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, '', []); 35 | $this->addOption('no-restart', 'N', InputOption::VALUE_NONE, 'Whether no need to restart server'); 36 | } 37 | 38 | public function handle() 39 | { 40 | $options = (array) include dirname(__DIR__, 2) . '/publish/watcher.php'; 41 | 42 | if (file_exists($configFile = $this->input->getOption('config'))) { 43 | $options = array_replace($options, (array) include $configFile); 44 | } elseif (file_exists($configFile = BASE_PATH . '/config/autoload/watcher.php')) { // Compatible with old version, will be removed in the v3.1. 45 | $options = array_replace($options, (array) include $configFile); 46 | } 47 | 48 | $option = make(Option::class, [ 49 | 'options' => $options, 50 | 'dir' => $this->input->getOption('dir'), 51 | 'file' => $this->input->getOption('file'), 52 | 'restart' => ! $this->input->getOption('no-restart'), 53 | ]); 54 | 55 | $watcher = make(Watcher::class, [ 56 | 'option' => $option, 57 | 'output' => $this->output, 58 | ]); 59 | 60 | $watcher->run(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ConfigProvider.php: -------------------------------------------------------------------------------- 1 | [ 24 | ], 25 | 'commands' => [ 26 | WatchCommand::class, 27 | ], 28 | 'listeners' => [ 29 | ReloadDotenvListener::class, 30 | ], 31 | 'publish' => [ 32 | [ 33 | 'id' => 'config', 34 | 'description' => 'The config for watcher.', 35 | 'source' => __DIR__ . '/../publish/watcher.php', 36 | 'destination' => BASE_PATH . '/.watcher.php', 37 | ], 38 | ], 39 | ]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Driver/AbstractDriver.php: -------------------------------------------------------------------------------- 1 | timer = new Timer(); 27 | } 28 | 29 | public function __destruct() 30 | { 31 | $this->stop(); 32 | } 33 | 34 | public function isDarwin(): bool 35 | { 36 | return PHP_OS === 'Darwin'; 37 | } 38 | 39 | public function stop() 40 | { 41 | if ($this->timerId) { 42 | $this->timer->clear($this->timerId); 43 | $this->timerId = null; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Driver/DriverInterface.php: -------------------------------------------------------------------------------- 1 | isDarwin()) { 33 | $ret = exec('which gfind'); 34 | if (empty($ret['output'])) { 35 | throw new InvalidArgumentException('gfind not exists. You can `brew install findutils` to install it.'); 36 | } 37 | } else { 38 | $ret = exec('which find'); 39 | if (empty($ret['output'])) { 40 | throw new InvalidArgumentException('find not exists.'); 41 | } 42 | $ret = exec('find --help'); 43 | $this->isSupportFloatMinutes = ! str_contains($ret['output'] ?? '', 'BusyBox'); 44 | } 45 | } 46 | 47 | public function watch(Channel $channel): void 48 | { 49 | $this->startTime = time(); 50 | $seconds = $this->option->getScanIntervalSeconds(); 51 | 52 | $this->timerId = $this->timer->tick($seconds, function () use ($channel) { 53 | global $fileModifyTimes; 54 | if (is_null($fileModifyTimes)) { 55 | $fileModifyTimes = []; 56 | } 57 | 58 | [$fileModifyTimes, $changedFiles] = $this->scan($fileModifyTimes, $this->getScanIntervalMinutes()); 59 | 60 | foreach ($changedFiles as $file) { 61 | $channel->push($file); 62 | } 63 | }); 64 | } 65 | 66 | protected function getScanIntervalMinutes(): string 67 | { 68 | $minutes = $this->option->getScanIntervalSeconds() / 60; 69 | if ($this->isSupportFloatMinutes) { 70 | return sprintf('-%.2f', $minutes); 71 | } 72 | return sprintf('-%d', ceil($minutes)); 73 | } 74 | 75 | protected function find(array $fileModifyTimes, array $targets, string $minutes, array $ext = []): array 76 | { 77 | $changedFiles = []; 78 | $dest = implode(' ', $targets); 79 | $ret = exec($this->getBin() . ' ' . $dest . ' -mmin ' . $minutes . ' -type f -print'); 80 | if ($ret['code'] === 0 && strlen($ret['output'])) { 81 | $stdout = trim($ret['output']); 82 | 83 | $lineArr = explode(PHP_EOL, $stdout); 84 | foreach ($lineArr as $line) { 85 | $pathName = $line; 86 | $modifyTime = filemtime($pathName); 87 | // modifyTime less than or equal to startTime continue 88 | if ($modifyTime <= $this->startTime) { 89 | continue; 90 | } 91 | if (! empty($ext) && ! Str::endsWith($pathName, $ext)) { 92 | continue; 93 | } 94 | 95 | if (isset($fileModifyTimes[$pathName]) && $fileModifyTimes[$pathName] == $modifyTime) { 96 | continue; 97 | } 98 | $fileModifyTimes[$pathName] = $modifyTime; 99 | $changedFiles[] = $pathName; 100 | } 101 | } 102 | 103 | return [$fileModifyTimes, $changedFiles]; 104 | } 105 | 106 | protected function getBin(): string 107 | { 108 | return $this->isDarwin() ? 'gfind' : 'find'; 109 | } 110 | 111 | protected function scan(array $fileModifyTimes, string $minutes): array 112 | { 113 | $ext = $this->option->getExt(); 114 | 115 | $dirs = array_map(fn ($dir) => BASE_PATH . '/' . $dir, $this->option->getWatchDir()); 116 | 117 | [$fileModifyTimes, $changedFilesInDirs] = $this->find($fileModifyTimes, $dirs, $minutes, $ext); 118 | 119 | $files = array_map(fn ($file) => BASE_PATH . '/' . $file, $this->option->getWatchFile()); 120 | 121 | [$fileModifyTimes, $changedFiles] = $this->find($fileModifyTimes, $files, $minutes); 122 | 123 | return [$fileModifyTimes, array_merge($changedFilesInDirs, $changedFiles)]; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Driver/FindNewerDriver.php: -------------------------------------------------------------------------------- 1 | ' . $this->getToModifyFile()); 39 | exec('echo 1 > ' . $this->getToScanFile()); 40 | } 41 | 42 | public function watch(Channel $channel): void 43 | { 44 | $seconds = $this->option->getScanIntervalSeconds(); 45 | $this->timerId = $this->timer->tick($seconds, function () use ($channel) { 46 | if ($this->scanning) { 47 | return; 48 | } 49 | $this->scanning = true; 50 | $changedFiles = $this->scan(); 51 | ++$this->count; 52 | // update mtime 53 | if ($changedFiles) { 54 | exec('echo 1 > ' . $this->getToModifyFile()); 55 | exec('echo 1 > ' . $this->getToScanFile()); 56 | } 57 | 58 | foreach ($changedFiles as $file) { 59 | $channel->push($file); 60 | $this->scanning = false; 61 | return; 62 | } 63 | $this->scanning = false; 64 | }); 65 | } 66 | 67 | protected function find(array $targets, array $ext = []): array 68 | { 69 | $changedFiles = []; 70 | 71 | $shell = ''; 72 | $len = count($targets); 73 | // merge find command 74 | for ($i = 0; $i < $len; ++$i) { 75 | $dest = $targets[$i]; 76 | $symbol = ($i == $len - 1) ? '' : '&'; 77 | $file = $this->getToScanFile(); 78 | $shell = $shell . sprintf('find %s -newer %s -type f', $dest, $file) . $symbol; 79 | } 80 | 81 | $ret = exec($shell); 82 | if ($ret['code'] === 0 && strlen($ret['output'])) { 83 | $stdout = $ret['output']; 84 | $lineArr = explode(PHP_EOL, $stdout); 85 | foreach ($lineArr as $pathName) { 86 | if (empty($pathName)) { 87 | continue; 88 | } 89 | 90 | if (! empty($ext) && ! Str::endsWith($pathName, $ext)) { 91 | continue; 92 | } 93 | $changedFiles[] = $pathName; 94 | } 95 | } 96 | 97 | return $changedFiles; 98 | } 99 | 100 | protected function scan(): array 101 | { 102 | $ext = $this->option->getExt(); 103 | $dirs = array_map(fn ($dir) => BASE_PATH . '/' . $dir, $this->option->getWatchDir()); 104 | $files = array_map(fn ($file) => BASE_PATH . '/' . $file, $this->option->getWatchFile()); 105 | 106 | if ($files) { 107 | $dirs[] = implode(' ', $files); 108 | } 109 | 110 | return $this->find($dirs, $ext); 111 | } 112 | 113 | protected function getToModifyFile(): string 114 | { 115 | return $this->tmpFile . ($this->count % 2); 116 | } 117 | 118 | protected function getToScanFile(): string 119 | { 120 | return $this->tmpFile . (($this->count + 1) % 2); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Driver/FswatchDriver.php: -------------------------------------------------------------------------------- 1 | getCmd(); 40 | $this->process = proc_open($cmd, [['pipe', 'r'], ['pipe', 'w']], $pipes); 41 | if (! is_resource($this->process)) { 42 | throw new RuntimeException('fswatch failed.'); 43 | } 44 | 45 | while (! $channel->isClosing()) { 46 | $ret = fread($pipes[1], 8192); 47 | if (is_string($ret) && $ret !== '') { 48 | Coroutine::create(function () use ($ret, $channel) { 49 | $files = array_filter(explode("\n", $ret)); 50 | foreach ($files as $file) { 51 | if (Str::endsWith($file, $this->option->getExt())) { 52 | $channel->push($file); 53 | } 54 | } 55 | }); 56 | } 57 | } 58 | } 59 | 60 | public function stop() 61 | { 62 | parent::stop(); 63 | 64 | if (is_resource($this->process)) { 65 | $running = proc_get_status($this->process)['running']; 66 | // Kill the child process to exit. 67 | $running && proc_terminate($this->process, SIGKILL); 68 | } 69 | } 70 | 71 | protected function getCmd(): string 72 | { 73 | $dir = $this->option->getWatchDir(); 74 | $file = $this->option->getWatchFile(); 75 | 76 | $cmd = 'fswatch '; 77 | if (! $this->isDarwin()) { 78 | $cmd .= ' -m inotify_monitor'; 79 | $cmd .= " -E --format '%p' -r "; 80 | $cmd .= ' --event Created --event Updated --event Removed --event Renamed '; 81 | } 82 | 83 | return $cmd . implode(' ', $dir) . ' ' . implode(' ', $file); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Driver/ScanFileDriver.php: -------------------------------------------------------------------------------- 1 | filesystem = new Filesystem(); 30 | } 31 | 32 | public function watch(Channel $channel): void 33 | { 34 | $seconds = $this->option->getScanIntervalSeconds(); 35 | $this->timerId = $this->timer->tick($seconds, function () use ($channel) { 36 | global $lastMD5; 37 | $files = []; 38 | $currentMD5 = $this->getWatchMD5($files); 39 | if ($lastMD5 && $lastMD5 !== $currentMD5) { 40 | $changeFilesMD5 = array_diff(array_values($lastMD5), array_values($currentMD5)); 41 | $addFiles = array_diff(array_keys($currentMD5), array_keys($lastMD5)); 42 | foreach ($addFiles as $file) { 43 | $channel->push($file); 44 | } 45 | $deleteFiles = array_diff(array_keys($lastMD5), array_keys($currentMD5)); 46 | $deleteCount = count($deleteFiles); 47 | 48 | $watchingLog = sprintf('%s Watching: Total:%d, Change:%d, Add:%d, Delete:%d.', self::class, count($currentMD5), count($changeFilesMD5), count($addFiles), $deleteCount); 49 | $this->logger->debug($watchingLog); 50 | 51 | if ($deleteCount == 0) { 52 | $changeFilesIdx = array_keys($changeFilesMD5); 53 | foreach ($changeFilesIdx as $idx) { 54 | isset($files[$idx]) && $channel->push($files[$idx]); 55 | } 56 | } else { 57 | $this->logger->warning('Delete files must be restarted manually to take effect.'); 58 | } 59 | } 60 | $lastMD5 = $currentMD5; 61 | }); 62 | } 63 | 64 | protected function getWatchMD5(&$files): array 65 | { 66 | $filesMD5 = []; 67 | $filesObj = []; 68 | $dir = $this->option->getWatchDir(); 69 | $ext = $this->option->getExt(); 70 | // Scan all watch dirs. 71 | foreach ($dir as $d) { 72 | $filesObj = array_merge($filesObj, $this->filesystem->allFiles(BASE_PATH . '/' . $d)); 73 | } 74 | /** @var SplFileInfo $obj */ 75 | foreach ($filesObj as $obj) { 76 | $pathName = $obj->getPathName(); 77 | if (Str::endsWith($pathName, $ext)) { 78 | $files[] = $pathName; 79 | $contents = file_get_contents($pathName); 80 | $filesMD5[$pathName] = md5($contents); 81 | } 82 | } 83 | // Scan all watch files. 84 | $file = $this->option->getWatchFile(); 85 | $filesObj = $this->filesystem->files(BASE_PATH, true); 86 | /** @var SplFileInfo $obj */ 87 | foreach ($filesObj as $obj) { 88 | $pathName = $obj->getPathName(); 89 | if (Str::endsWith($pathName, $file)) { 90 | $files[] = $pathName; 91 | $contents = file_get_contents($pathName); 92 | $filesMD5[$pathName] = md5($contents); 93 | } 94 | } 95 | return $filesMD5; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Event/BeforeServerRestart.php: -------------------------------------------------------------------------------- 1 | reloadDotenv(); 36 | } 37 | 38 | private function reloadDotenv(): void 39 | { 40 | if (file_exists(BASE_PATH . '/.env')) { 41 | DotenvManager::reload([BASE_PATH]); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Option.php: -------------------------------------------------------------------------------- 1 | driver = $options['driver']; 45 | isset($options['bin']) && $this->bin = $options['bin']; 46 | isset($options['command']) && $this->command = $options['command']; 47 | isset($options['watch']['dir']) && $this->watchDir = (array) $options['watch']['dir']; 48 | isset($options['watch']['file']) && $this->watchFile = (array) $options['watch']['file']; 49 | isset($options['watch']['scan_interval']) && $this->scanInterval = (int) $options['watch']['scan_interval']; 50 | isset($options['ext']) && $this->ext = (array) $options['ext']; 51 | 52 | $this->watchDir = array_unique(array_merge($this->watchDir, $dir)); 53 | $this->watchFile = array_unique(array_merge($this->watchFile, $file)); 54 | } 55 | 56 | public function getDriver(): string 57 | { 58 | return $this->driver; 59 | } 60 | 61 | public function getBin(): string 62 | { 63 | return $this->bin; 64 | } 65 | 66 | public function getCommand(): string 67 | { 68 | return $this->command; 69 | } 70 | 71 | public function getWatchDir(): array 72 | { 73 | return $this->watchDir; 74 | } 75 | 76 | public function getWatchFile(): array 77 | { 78 | return $this->watchFile; 79 | } 80 | 81 | public function getExt(): array 82 | { 83 | return $this->ext; 84 | } 85 | 86 | public function getScanInterval(): int 87 | { 88 | return $this->scanInterval > 0 ? $this->scanInterval : 2000; 89 | } 90 | 91 | public function getScanIntervalSeconds(): float 92 | { 93 | return $this->getScanInterval() / 1000; 94 | } 95 | 96 | public function isRestart(): bool 97 | { 98 | return $this->restart; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Process.php: -------------------------------------------------------------------------------- 1 | ast = new Ast(); 45 | $this->config = $this->initScanConfig(); 46 | $this->reader = new AnnotationReader($this->config->getIgnoreAnnotations()); 47 | $this->filesystem = new Filesystem(); 48 | } 49 | 50 | public function __invoke() 51 | { 52 | $meta = $this->getMetadata($this->file); 53 | if ($meta === null) { 54 | return; 55 | } 56 | $class = $meta->toClassName(); 57 | $collectors = $this->config->getCollectors(); 58 | [$data, $proxies, $aspectClasses] = file_exists($this->path) ? unserialize(file_get_contents($this->path)) : [[], [], []]; 59 | foreach ($data as $collector => $deserialized) { 60 | /** @var MetadataCollector $collector */ 61 | if (in_array($collector, $collectors)) { 62 | $collector::deserialize($deserialized); 63 | } 64 | } 65 | 66 | if (! empty($this->file)) { 67 | require $this->file; 68 | } 69 | 70 | // Collect the annotations. 71 | $ref = ReflectionManager::reflectClass($class); 72 | foreach ($collectors as $collector) { 73 | $collector::clear($class); 74 | } 75 | 76 | $scanner = new Scanner($this->config, new NullScanHandler()); 77 | $scanner->collect($this->reader, $ref); 78 | 79 | $collectors = $this->config->getCollectors(); 80 | $data = []; 81 | /** @var MetadataCollector|string $collector */ 82 | foreach ($collectors as $collector) { 83 | $data[$collector] = $collector::serialize(); 84 | } 85 | 86 | $composerLoader = Composer::getLoader(); 87 | $composerLoader->addClassMap($this->config->getClassMap()); 88 | $this->deleteAspectClasses($aspectClasses, $proxies, $class); 89 | 90 | // Reload the proxy class. 91 | $manager = new ProxyManager(array_merge($composerLoader->getClassMap(), $proxies, [$class => $this->file]), BASE_PATH . '/runtime/container/proxy/'); 92 | $proxies = $manager->getProxies(); 93 | $aspectClasses = $manager->getAspectClasses(); 94 | 95 | $this->putCache($this->path, serialize([$data, $proxies, $aspectClasses])); 96 | } 97 | 98 | protected function putCache($path, $data) 99 | { 100 | if (! $this->filesystem->isDirectory($dir = dirname($path))) { 101 | $this->filesystem->makeDirectory($dir, 0755, true); 102 | } 103 | 104 | $this->filesystem->put($path, $data); 105 | } 106 | 107 | protected function getMetadata(string $file): ?Metadata 108 | { 109 | $stmts = $this->ast->parse($this->filesystem->get($file)); 110 | $meta = new Metadata(); 111 | $meta->path = $file; 112 | $traverser = new NodeTraverser(); 113 | $traverser->addVisitor(new RewriteClassNameVisitor($meta)); 114 | $traverser->traverse($stmts); 115 | if (! $meta->isClass()) { 116 | return null; 117 | } 118 | return $meta; 119 | } 120 | 121 | protected function initScanConfig(): ScanConfig 122 | { 123 | return ScanConfig::instance(BASE_PATH . '/config/'); 124 | } 125 | 126 | protected function deleteAspectClasses($aspectClasses, $proxies, $class): void 127 | { 128 | foreach ($aspectClasses as $aspect => $classes) { 129 | if ($aspect !== $class) { 130 | continue; 131 | } 132 | foreach ($classes as $path) { 133 | if (file_exists($path)) { 134 | unlink($path); 135 | } 136 | } 137 | } 138 | 139 | $classesAspects = AspectCollector::get('classes', []); 140 | foreach ($classesAspects as $aspect => $rules) { 141 | if ($aspect !== $class) { 142 | continue; 143 | } 144 | foreach ($rules as $rule) { 145 | if (isset($proxies[$rule]) && file_exists($proxies[$rule])) { 146 | unlink($proxies[$rule]); 147 | } 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Watcher.php: -------------------------------------------------------------------------------- 1 | driver = $this->getDriver(); 51 | $this->filesystem = new Filesystem(); 52 | $json = Json::decode($this->filesystem->get(BASE_PATH . '/composer.json')); 53 | $this->autoload = array_flip($json['autoload']['psr-4'] ?? []); 54 | $this->config = $container->get(ConfigInterface::class); 55 | $this->printer = new Standard(); 56 | $this->channel = new Channel(1); 57 | $this->channel->push(true); 58 | } 59 | 60 | public function run() 61 | { 62 | $this->dumpAutoload(); 63 | $this->restart(true); 64 | 65 | $channel = new Channel(999); 66 | Coroutine::create(function () use ($channel) { 67 | $this->driver->watch($channel); 68 | }); 69 | 70 | $result = []; 71 | while (true) { 72 | $file = $channel->pop(0.001); 73 | if ($file === false) { 74 | if (count($result) > 0) { 75 | $result = []; 76 | $this->restart(false); 77 | } 78 | } else { 79 | $ret = exec(sprintf('%s %s/vendor/hyperf/watcher/collector-reload.php %s', $this->option->getBin(), BASE_PATH, $file)); 80 | if (isset($ret['code']) && $ret['code'] === 0) { 81 | $this->output->writeln('Class reload success.'); 82 | } else { 83 | $this->output->writeln('Class reload failed.'); 84 | $this->output->writeln($ret['output'] ?? ''); 85 | } 86 | $result[] = $file; 87 | } 88 | } 89 | } 90 | 91 | public function dumpAutoload() 92 | { 93 | $ret = exec('composer dump-autoload -o --no-scripts -d ' . BASE_PATH); 94 | $this->output->writeln($ret['output'] ?? ''); 95 | } 96 | 97 | public function restart($isStart = true) 98 | { 99 | if (! $this->option->isRestart()) { 100 | return; 101 | } 102 | $file = $this->config->get('server.settings.pid_file'); 103 | if (empty($file)) { 104 | throw new FileNotFoundException('The config of pid_file is not found.'); 105 | } 106 | $daemonize = $this->config->get('server.settings.daemonize', false); 107 | if ($daemonize) { 108 | throw new InvalidArgumentException('Please set `server.settings.daemonize` to false'); 109 | } 110 | if (! $isStart && $this->filesystem->exists($file)) { 111 | $pid = $this->filesystem->get($file); 112 | try { 113 | $this->output->writeln('Stop server...'); 114 | $this->container->get(EventDispatcherInterface::class) 115 | ->dispatch(new BeforeServerRestart($pid)); 116 | if (posix_kill((int) $pid, 0)) { 117 | posix_kill((int) $pid, SIGTERM); 118 | } 119 | } catch (Throwable) { 120 | $this->output->writeln('Stop server failed. Please execute `composer dump-autoload -o`'); 121 | } 122 | } 123 | 124 | Coroutine::create(function () { 125 | $this->channel->pop(); 126 | $this->output->writeln('Start server ...'); 127 | 128 | $descriptorSpec = [ 129 | 0 => STDIN, 130 | 1 => STDOUT, 131 | 2 => STDERR, 132 | ]; 133 | 134 | proc_open( 135 | command: $this->option->getBin() . ' ' . BASE_PATH . '/' . $this->option->getCommand(), 136 | descriptor_spec: $descriptorSpec, 137 | pipes: $pipes 138 | ); 139 | 140 | $this->output->writeln('Stop server success.'); 141 | $this->channel->push(1); 142 | }); 143 | } 144 | 145 | protected function getDriver() 146 | { 147 | $driver = $this->option->getDriver(); 148 | if (! class_exists($driver)) { 149 | throw new \InvalidArgumentException('Driver not support.'); 150 | } 151 | return make($driver, ['option' => $this->option]); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /watcher.php: -------------------------------------------------------------------------------- 1 |