diff --git a/src/Status.php b/src/Status.php index dc5f51d..ef64a7c 100644 --- a/src/Status.php +++ b/src/Status.php @@ -23,13 +23,9 @@ class Status { const ENV_RESTART = 'XDEBUG_HANDLER_RESTART'; - const CHECK = 'Check'; - const ERROR = 'Error'; - const INFO = 'Info'; - const NORESTART = 'NoRestart'; - const RESTART = 'Restart'; - const RESTARTING = 'Restarting'; - const RESTARTED = 'Restarted'; + + /** @var bool */ + private $active; /** @var bool */ private $debug; @@ -55,16 +51,23 @@ class Status /** * @param string $envAllowXdebug Prefixed _ALLOW_XDEBUG name * @param bool $debug Whether debug output is required + * @param string|null $version Xdebug version + * @param string|null $mode Xdebug mode */ - public function __construct(string $envAllowXdebug, bool $debug) + public function __construct(string $envAllowXdebug, bool $debug, ?string $version, ?string $mode) { $start = getenv(self::ENV_RESTART); - Process::setEnv(self::ENV_RESTART); + putenv(self::ENV_RESTART); $this->ellapsed = is_numeric($start) ? (int) round((microtime(true) - $start) * 1000) : 0; $this->envAllowXdebug = $envAllowXdebug; - $this->debug = $debug && defined('STDERR'); - $this->modeOff = false; + $this->active = $this->debug = $debug && defined('STDERR'); + + if ((string) $version !== '') { + $this->loaded = '('.$version.')'.((string) $mode !== '' ? ' xdebug.mode='.$mode : ''); + } + + $this->modeOff = $mode === 'off'; $this->pid = (int) getmypid(); } @@ -76,95 +79,84 @@ public function __construct(string $envAllowXdebug, bool $debug) public function setLogger(LoggerInterface $logger): void { $this->logger = $logger; + $this->active = true; } /** - * Calls a handler method to report a message - * - * @throws \InvalidArgumentException If $op is not known + * Checking status messages */ - public function report(string $op, ?string $data): void + public function reportCheck(): void { - if ($this->logger !== null || $this->debug) { - $param = (string) $data; - - switch($op) { - case self::CHECK: - $this->reportCheck($param); - break; - case self::ERROR: - $this->reportError($param); - break; - case self::INFO: - $this->reportInfo($param); - break; - case self::NORESTART: - $this->reportNoRestart(); - break; - case self::RESTART: - $this->reportRestart(); - break; - case self::RESTARTED: - $this->reportRestarted(); - break; - case self::RESTARTING: - $this->reportRestarting($param); - break; - default: - throw new \InvalidArgumentException('Unknown op handler: '.$op); - } + if ($this->active) { + $this->output('Checking '.$this->envAllowXdebug); } } /** - * Outputs a status message + * Shows the loaded status and saves the current time */ - private function output(string $text, ?string $level = null): void + public function reportRequired(): void { - if ($this->logger !== null) { - $this->logger->log($level !== null ? $level: LogLevel::DEBUG, $text); - } - - if ($this->debug) { - fwrite(STDERR, sprintf('xdebug-handler[%d] %s', $this->pid, $text.PHP_EOL)); + if ($this->active) { + $this->output($this->getLoadedMessage()); + // Set the timer environment variable + putenv(self::ENV_RESTART.'='.(string) microtime(true)); } } /** - * Checking status message + * Restarting status message + * + * @param list|string $command */ - private function reportCheck(string $loaded): void + public function reportRestarting($command): void { - list($version, $mode) = explode('|', $loaded); + if (!$this->active) { + return; + } - if ($version !== '') { - $this->loaded = '('.$version.')'.($mode !== '' ? ' xdebug.mode='.$mode : ''); + $text = sprintf('Process restarting (%s)', $this->getEnvAllow()); + $this->output($text); + + if (is_array($command)) { + $command = sprintf('[%s]', implode(', ', $command)); } - $this->modeOff = $mode === 'off'; - $this->output('Checking '.$this->envAllowXdebug); + $text = 'Running: '.$command; + $this->output($text); } /** - * Error status message + * Exit message */ - private function reportError(string $error): void + public function reportExit(int $exitCode): void { - $this->output(sprintf('No restart (%s)', $error), LogLevel::WARNING); + if ($this->active) { + $this->output('Restarted process exited '.$exitCode); + } } /** - * Info status message + * Restarted status message */ - private function reportInfo(string $info): void + public function reportRestarted(): void { - $this->output($info); + if ($this->active) { + $loaded = $this->getLoadedMessage(); + $text = sprintf('Restarted (%d ms). %s', $this->ellapsed, $loaded); + $level = $this->loaded !== null ? LogLevel::WARNING : null; + $this->output($text, $level); + } } /** * No restart status message */ - private function reportNoRestart(): void + public function reportNoRestart(): void { + if (!$this->active) { + return; + } + $this->output($this->getLoadedMessage()); if ($this->loaded !== null) { @@ -177,34 +169,57 @@ private function reportNoRestart(): void } /** - * Restart status message + * Existing restart settings message */ - private function reportRestart(): void + public function reportExisting(): void { - $this->output($this->getLoadedMessage()); - Process::setEnv(self::ENV_RESTART, (string) microtime(true)); + if ($this->active) { + $this->output('Process called with existing restart settings'); + } } /** - * Restarted status message + * Configuration error, logged as a warning */ - private function reportRestarted(): void + public function configError(string $error): void { - $loaded = $this->getLoadedMessage(); - $text = sprintf('Restarted (%d ms). %s', $this->ellapsed, $loaded); - $level = $this->loaded !== null ? LogLevel::WARNING : null; - $this->output($text, $level); + if ($this->active) { + $this->output(sprintf('No restart (%s)', $error), LogLevel::WARNING); + } } /** - * Restarting status message + * General info */ - private function reportRestarting(string $command): void + public function info(string $info): void { - $text = sprintf('Process restarting (%s)', $this->getEnvAllow()); - $this->output($text); - $text = 'Running: '.$command; - $this->output($text); + if ($this->active) { + $this->output($info); + } + } + + /** + * General error, logged as a warning + */ + public function error(string $error): void + { + if ($this->active) { + $this->output($error, LogLevel::WARNING); + } + } + + /** + * Outputs a status message + */ + private function output(string $text, ?string $level = null): void + { + if ($this->logger !== null) { + $this->logger->log($level !== null ? $level: LogLevel::DEBUG, $text); + } + + if ($this->debug) { + fwrite(STDERR, sprintf('xdebug-handler[%d] %s', $this->pid, $text.PHP_EOL)); + } } /** diff --git a/src/XdebugHandler.php b/src/XdebugHandler.php index 4e6eb75..6d13dfd 100644 --- a/src/XdebugHandler.php +++ b/src/XdebugHandler.php @@ -98,7 +98,12 @@ public function __construct(string $envPrefix) $this->debug = (string) getenv(self::DEBUG); } - $this->statusWriter = new Status($this->envAllowXdebug, (bool) $this->debug); + $this->statusWriter = new Status( + $this->envAllowXdebug, + (bool) $this->debug, + self::$xdebugVersion, + self::$xdebugMode + ); } /** @@ -137,12 +142,12 @@ public function setPersistent(): self */ public function check(): void { - $this->notify(Status::CHECK, self::$xdebugVersion.'|'.self::$xdebugMode); + $this->statusWriter->reportCheck(); $envArgs = explode('|', (string) getenv($this->envAllowXdebug)); if (!((bool) $envArgs[0]) && $this->requiresRestart(self::$xdebugActive)) { // Restart required - $this->notify(Status::RESTART); + $this->statusWriter->reportRequired(); $command = $this->prepareRestart(); if ($command !== null) { @@ -153,7 +158,7 @@ public function check(): void if (self::RESTART_ID === $envArgs[0] && count($envArgs) === 5) { // Restarted, so unset environment variable and use saved values - $this->notify(Status::RESTARTED); + $this->statusWriter->reportRestarted(); Process::setEnv($this->envAllowXdebug); self::$inRestart = true; @@ -170,12 +175,13 @@ public function check(): void return; } - $this->notify(Status::NORESTART); + $this->statusWriter->reportNoRestart(); $settings = self::getRestartSettings(); if ($settings !== null) { // Called with existing settings, so sync our settings $this->syncSettings($settings); + $this->statusWriter->reportExisting(); } } @@ -284,18 +290,16 @@ private function doRestart(array $command): void { if (PHP_VERSION_ID >= 70400) { $cmd = $command; - $displayCmd = sprintf('[%s]', implode(', ', $cmd)); } else { $cmd = Process::escapeShellCommand($command); if (defined('PHP_WINDOWS_VERSION_BUILD')) { // Outer quotes required on cmd string below PHP 8 $cmd = '"'.$cmd.'"'; } - $displayCmd = $cmd; } $this->tryEnableSignals(); - $this->notify(Status::RESTARTING, $displayCmd); + $this->statusWriter->reportRestarting($cmd); $process = proc_open($cmd, [], $pipes); if (is_resource($process)) { @@ -304,14 +308,14 @@ private function doRestart(array $command): void if (!isset($exitCode)) { // Unlikely that php or the default shell cannot be invoked - $this->notify(Status::ERROR, 'Unable to restart process'); + $this->statusWriter->error('Unable to restart process'); $exitCode = -1; } else { - $this->notify(Status::INFO, 'Restarted process exited '.$exitCode); + $this->statusWriter->reportExit($exitCode); } if ($this->debug === '2') { - $this->notify(Status::INFO, 'Temp ini saved: '.$this->tmpIni); + $this->statusWriter->info('Temp ini saved: '.$this->tmpIni); } else { @unlink((string) $this->tmpIni); } @@ -332,23 +336,23 @@ private function doRestart(array $command): void private function prepareRestart(): ?array { if (!$this->cli) { - $this->notify(Status::ERROR, 'Unsupported SAPI: '.PHP_SAPI); + $this->statusWriter->configError('Unsupported SAPI: '.PHP_SAPI); return null; } if (($argv = $this->checkServerArgv()) === null) { - $this->notify(Status::ERROR, '$_SERVER[argv] is not as expected'); + $this->statusWriter->configError('$_SERVER[argv] is not as expected'); return null; } if (!$this->checkConfiguration($info)) { - $this->notify(Status::ERROR, $info); + $this->statusWriter->configError((string) $info); return null; } $mainScript = (string) $this->script; if (!$this->checkMainScript($mainScript, $argv)) { - $this->notify(Status::ERROR, 'Unable to access main script: '.$mainScript); + $this->statusWriter->configError('Unable to access main script: '.$mainScript); return null; } @@ -356,7 +360,7 @@ private function prepareRestart(): ?array $iniError = 'Unable to create temp ini file at: '.$tmpDir; if (($tmpfile = @tempnam($tmpDir, '')) === false) { - $this->notify(Status::ERROR, $iniError); + $this->statusWriter->configError($iniError); return null; } @@ -365,13 +369,13 @@ private function prepareRestart(): ?array $scannedInis = count($iniFiles) > 1; if (!$this->writeTmpIni($tmpfile, $iniFiles, $error)) { - $this->notify(Status::ERROR, $error ?? $iniError); + $this->statusWriter->configError($error ?? $iniError); @unlink($tmpfile); return null; } if (!$this->setEnvironment($scannedInis, $iniFiles, $tmpfile)) { - $this->notify(Status::ERROR, 'Unable to set environment variables'); + $this->statusWriter->configError('Unable to set environment variables'); @unlink($tmpfile); return null; } @@ -482,14 +486,6 @@ private function setEnvironment(bool $scannedInis, array $iniFiles, string $tmpI return putenv($this->envAllowXdebug.'='.implode('|', $envArgs)); } - /** - * Logs status messages - */ - private function notify(string $op, ?string $data = null): void - { - $this->statusWriter->report($op, $data); - } - /** * Returns default, changed and command-line ini settings * @@ -577,7 +573,6 @@ private function syncSettings(array $settings): void } self::$skipped = $settings['skipped']; - $this->notify(Status::INFO, 'Process called with existing restart settings'); } /**