diff --git a/README.md b/README.md index 925faaa..b060155 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,31 @@ or using CLI: php-watcher server.php --exec php7 ``` +## Gracefully reloading down your script + +It is possible to have PHP-watcher send any signal that you specify to your + application. + +```bash +php-watcher --signal SIGTERM server.php +``` + +Your application can handle the signal as follows: + +```php + $input->getOption('watch'), 'extensions' => empty($input->getOption('ext')) ? [] : explode(',', $input->getOption('ext')), 'ignore' => $input->getOption('ignore'), + 'signal' => $input->getOption('signal') ? constant($input->getOption('signal')) : null, 'delay' => (float)$input->getOption('delay'), 'arguments' => $input->getOption('arguments'), ]; diff --git a/src/Config/Config.php b/src/Config/Config.php index 5d29428..deb888c 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -6,13 +6,16 @@ final class Config { private const DEFAULT_PHP_EXECUTABLE = 'php'; private const DEFAULT_DELAY_IN_SECONDS = 0.25; - - private $delay; + private const DEFAULT_SIGNAL = SIGINT; private $script; private $phpExecutable; + private $signal; + + private $delay; + /** * @var string[] */ @@ -20,11 +23,12 @@ final class Config private $watchList; - public function __construct(string $script, ?string $phpExecutable, ?float $delay, array $arguments, WatchList $watchList) + public function __construct(string $script, ?string $phpExecutable, ?int $signal, ?float $delay, array $arguments, WatchList $watchList) { $this->script = $script; - $this->delay = $delay ?: self::DEFAULT_DELAY_IN_SECONDS; $this->phpExecutable = $phpExecutable ?: self::DEFAULT_PHP_EXECUTABLE; + $this->signal = $signal ?: self::DEFAULT_SIGNAL; + $this->delay = $delay ?: self::DEFAULT_DELAY_IN_SECONDS; $this->arguments = $arguments; $this->watchList = $watchList; } @@ -36,11 +40,22 @@ public function watchList(): WatchList public function command(): string { - return implode(' ', [$this->phpExecutable, $this->script, implode(' ', $this->arguments)]); + $commandline = implode(' ', [$this->phpExecutable, $this->script, implode(' ', $this->arguments)]); + if ('\\' !== DIRECTORY_SEPARATOR) { + // exec is mandatory to deal with sending a signal to the process + $commandline = 'exec '.$commandline; + } + + return $commandline; } public function delay(): float { return $this->delay; } + + public function signal(): int + { + return $this->signal; + } } diff --git a/src/Screen/Screen.php b/src/Screen/Screen.php index 556521a..a6d0ef8 100644 --- a/src/Screen/Screen.php +++ b/src/Screen/Screen.php @@ -61,11 +61,13 @@ private function info(string $text): void public function start(string $command): void { - $this->info(sprintf('starting `%s`', str_replace("'", '', trim($command)))); + $command = str_replace('exec', '', $command); + $this->info(sprintf('starting `%s`', trim($command))); } public function restarting(string $command): void { + $this->output->writeln(''); $this->spinner->erase(); $this->output->writeln(''); $this->info('restarting due to changes...'); diff --git a/src/Watcher/Watcher.php b/src/Watcher/Watcher.php index f75686d..5c9001d 100644 --- a/src/Watcher/Watcher.php +++ b/src/Watcher/Watcher.php @@ -22,14 +22,14 @@ public function __construct(LoopInterface $loop, Screen $screen, ChangesListener $this->filesystemListener = $filesystemListener; } - public function startWatching(Process $process, float $delayToRestart): void + public function startWatching(Process $process, int $signal, float $delayToRestart): void { $this->screen->start($process->getCommand()); $this->screen->showSpinner($this->loop); $this->startProcess($process); - $this->filesystemListener->start(function () use ($process, $delayToRestart) { - $process->terminate(); + $this->filesystemListener->start(function () use ($process, $signal, $delayToRestart) { + $process->terminate($signal); $this->screen->restarting($process->getCommand()); $this->loop->addTimer($delayToRestart, function () use ($process) { diff --git a/src/WatcherCommand.php b/src/WatcherCommand.php index eb22fb4..ea56ef2 100644 --- a/src/WatcherCommand.php +++ b/src/WatcherCommand.php @@ -27,6 +27,7 @@ protected function configure(): void ->addOption('ignore', '-i', InputOption::VALUE_IS_ARRAY + InputOption::VALUE_OPTIONAL, 'Paths to ignore', []) ->addOption('exec', null, InputOption::VALUE_OPTIONAL, 'PHP executable') ->addOption('delay', null, InputOption::VALUE_OPTIONAL, 'Delaying restart') + ->addOption('signal', null, InputOption::VALUE_OPTIONAL, 'Signal to reload the app') ->addOption('arguments', null, InputOption::VALUE_IS_ARRAY + InputOption::VALUE_OPTIONAL, 'Arguments for the script', []) ->addOption('config', null, InputOption::VALUE_OPTIONAL, 'Path to config file'); } @@ -42,6 +43,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $screen->showOptions($config->watchList()); $process = new Process($config->command()); - $watcher->startWatching($process, $config->delay()); + $watcher->startWatching($process, $config->signal(), $config->delay()); } } diff --git a/tests/Helper/Filesystem.php b/tests/Helper/Filesystem.php index 68eb5e9..56fa540 100644 --- a/tests/Helper/Filesystem.php +++ b/tests/Helper/Filesystem.php @@ -24,6 +24,29 @@ public static function createHelloWorldPHPFile(): string return $name; } + public static function createHelloWorldPHPFileWithSignalsHandling(): string + { + $name = self::FIXTURES_DIR . 'test.php'; + $code = <<markTestSkipped('SIGTERM is not defined'); + } + + $scriptToRun = Filesystem::createHelloWorldPHPFileWithSignalsHandling(); + $watcher = (new WatcherRunner)->run($scriptToRun, ['--signal', 'SIGTERM', '--watch', __DIR__]); + $this->wait(); + + Filesystem::createHelloWorldPHPFile(); + $this->wait(); + + $output = $watcher->getOutput(); + + $this->assertStringContainsString(SIGTERM . ' signal was received', $output); + } +}