Dep: update
주로 PHAN
This commit is contained in:
+126
-42
@@ -30,13 +30,14 @@ class XdebugHandler
|
||||
private static $inRestart;
|
||||
private static $name;
|
||||
private static $skipped;
|
||||
private static $xdebugActive;
|
||||
|
||||
private $cli;
|
||||
private $colorOption;
|
||||
private $debug;
|
||||
private $envAllowXdebug;
|
||||
private $envOriginalInis;
|
||||
private $loaded;
|
||||
private $mode;
|
||||
private $persistent;
|
||||
private $script;
|
||||
/** @var Status|null */
|
||||
@@ -50,12 +51,11 @@ class XdebugHandler
|
||||
* would result in MYAPP_ALLOW_XDEBUG and MYAPP_ORIGINAL_INIS.
|
||||
*
|
||||
* @param string $envPrefix Value used in environment variables
|
||||
* @param string $colorOption Command-line long option to force color output
|
||||
* @throws \RuntimeException If a parameter is invalid
|
||||
* @throws \RuntimeException If the parameter is invalid
|
||||
*/
|
||||
public function __construct($envPrefix, $colorOption = '')
|
||||
public function __construct($envPrefix)
|
||||
{
|
||||
if (!is_string($envPrefix) || empty($envPrefix) || !is_string($colorOption)) {
|
||||
if (!is_string($envPrefix) || empty($envPrefix)) {
|
||||
throw new \RuntimeException('Invalid constructor parameter');
|
||||
}
|
||||
|
||||
@@ -63,13 +63,23 @@ class XdebugHandler
|
||||
$this->envAllowXdebug = self::$name.self::SUFFIX_ALLOW;
|
||||
$this->envOriginalInis = self::$name.self::SUFFIX_INIS;
|
||||
|
||||
$this->colorOption = $colorOption;
|
||||
|
||||
if (extension_loaded('xdebug')) {
|
||||
$ext = new \ReflectionExtension('xdebug');
|
||||
$this->loaded = $ext->getVersion() ?: 'unknown';
|
||||
$this->loaded = phpversion('xdebug') ?: 'unknown';
|
||||
|
||||
if (version_compare($this->loaded, '3.1', '>=')) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$modes = xdebug_info('mode');
|
||||
$this->mode = empty($modes) ? 'off' : implode(',', $modes);
|
||||
} elseif (false !== ($mode = ini_get('xdebug.mode'))) {
|
||||
$this->mode = getenv('XDEBUG_MODE') ?: ($mode ?: 'off');
|
||||
if (preg_match('/^,+$/', str_replace(' ', '', $this->mode))) {
|
||||
$this->mode = 'off';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self::$xdebugActive = $this->loaded && $this->mode !== 'off';
|
||||
|
||||
if ($this->cli = PHP_SAPI === 'cli') {
|
||||
$this->debug = getenv(self::DEBUG);
|
||||
}
|
||||
@@ -123,23 +133,22 @@ class XdebugHandler
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
$this->notify(Status::CHECK, $this->loaded);
|
||||
$this->notify(Status::CHECK, $this->loaded.'|'.$this->mode);
|
||||
$envArgs = explode('|', (string) getenv($this->envAllowXdebug));
|
||||
|
||||
if (empty($envArgs[0]) && $this->requiresRestart((bool) $this->loaded)) {
|
||||
if (empty($envArgs[0]) && $this->requiresRestart(self::$xdebugActive)) {
|
||||
// Restart required
|
||||
$this->notify(Status::RESTART);
|
||||
|
||||
if ($this->prepareRestart()) {
|
||||
$command = $this->getCommand();
|
||||
$this->notify(Status::RESTARTING, $command);
|
||||
$this->restart($command);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (self::RESTART_ID === $envArgs[0] && count($envArgs) === 5) {
|
||||
// Restarting, so unset environment variable and use saved values
|
||||
// Restarted, so unset environment variable and use saved values
|
||||
$this->notify(Status::RESTARTED);
|
||||
|
||||
Process::setEnv($this->envAllowXdebug);
|
||||
@@ -150,6 +159,8 @@ class XdebugHandler
|
||||
self::$skipped = $envArgs[1];
|
||||
}
|
||||
|
||||
$this->tryEnableSignals();
|
||||
|
||||
// Put restart settings in the environment
|
||||
$this->setEnvRestartSettings($envArgs);
|
||||
return;
|
||||
@@ -204,7 +215,7 @@ class XdebugHandler
|
||||
|
||||
if (count($envArgs) !== 6
|
||||
|| (!self::$inRestart && php_ini_loaded_file() !== $envArgs[0])) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
return array(
|
||||
@@ -228,21 +239,39 @@ class XdebugHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if Xdebug is loaded, or as directed by an extending class
|
||||
* Returns whether Xdebug is loaded and active
|
||||
*
|
||||
* @param bool $isLoaded Whether Xdebug is loaded
|
||||
* true: if Xdebug is loaded and is running in an active mode.
|
||||
* false: if Xdebug is not loaded, or it is running with xdebug.mode=off.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function requiresRestart($isLoaded)
|
||||
public static function isXdebugActive()
|
||||
{
|
||||
return $isLoaded;
|
||||
return self::$xdebugActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows an extending class to decide if there should be a restart
|
||||
*
|
||||
* The default is to restart if Xdebug is loaded and its mode is not "off".
|
||||
* Do not typehint for 1.x compatibility.
|
||||
*
|
||||
* @param bool $default The default behaviour
|
||||
*
|
||||
* @return bool Whether the process should restart
|
||||
*/
|
||||
protected function requiresRestart($default)
|
||||
{
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows an extending class to access the tmpIni
|
||||
*
|
||||
* @param string $command
|
||||
* Do not typehint for 1.x compatibility
|
||||
*
|
||||
* @param array $command
|
||||
*/
|
||||
protected function restart($command)
|
||||
{
|
||||
@@ -252,12 +281,35 @@ class XdebugHandler
|
||||
/**
|
||||
* Executes the restarted command then deletes the tmp ini
|
||||
*
|
||||
* @param string $command
|
||||
* @param array $command
|
||||
*/
|
||||
private function doRestart($command)
|
||||
private function doRestart(array $command)
|
||||
{
|
||||
passthru($command, $exitCode);
|
||||
$this->notify(Status::INFO, 'Restarted process exited '.$exitCode);
|
||||
$this->tryEnableSignals();
|
||||
$this->notify(Status::RESTARTING, implode(' ', $command));
|
||||
|
||||
if (PHP_VERSION_ID >= 70400) {
|
||||
$cmd = $command;
|
||||
} else {
|
||||
$cmd = Process::escapeShellCommand($command);
|
||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
||||
// Outer quotes required on cmd string below PHP 8
|
||||
$cmd = '"'.$cmd.'"';
|
||||
}
|
||||
}
|
||||
|
||||
$process = proc_open($cmd, array(), $pipes);
|
||||
if (is_resource($process)) {
|
||||
$exitCode = proc_close($process);
|
||||
}
|
||||
|
||||
if (!isset($exitCode)) {
|
||||
// Unlikely that php or the default shell cannot be invoked
|
||||
$this->notify(Status::ERROR, 'Unable to restart process');
|
||||
$exitCode = -1;
|
||||
} else {
|
||||
$this->notify(Status::INFO, 'Restarted process exited '.$exitCode);
|
||||
}
|
||||
|
||||
if ($this->debug === '2') {
|
||||
$this->notify(Status::INFO, 'Temp ini saved: '.$this->tmpIni);
|
||||
@@ -329,7 +381,8 @@ class XdebugHandler
|
||||
}
|
||||
|
||||
$content = '';
|
||||
$regex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi';
|
||||
$sectionRegex = '/^\s*\[(?:PATH|HOST)\s*=/mi';
|
||||
$xdebugRegex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi';
|
||||
|
||||
foreach ($iniFiles as $file) {
|
||||
// Check for inaccessible ini files
|
||||
@@ -337,7 +390,11 @@ class XdebugHandler
|
||||
$error = 'Unable to read ini: '.$file;
|
||||
return false;
|
||||
}
|
||||
$content .= preg_replace($regex, ';$1', $data).PHP_EOL;
|
||||
// Check and remove directives after HOST and PATH sections
|
||||
if (preg_match($sectionRegex, $data, $matches, PREG_OFFSET_CAPTURE)) {
|
||||
$data = substr($data, 0, $matches[0][1]);
|
||||
}
|
||||
$content .= preg_replace($xdebugRegex, ';$1', $data).PHP_EOL;
|
||||
}
|
||||
|
||||
// Merge loaded settings into our ini content, if it is valid
|
||||
@@ -353,9 +410,9 @@ class XdebugHandler
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the restart command line
|
||||
* Returns the command line arguments for the restart
|
||||
*
|
||||
* @return string
|
||||
* @return array
|
||||
*/
|
||||
private function getCommand()
|
||||
{
|
||||
@@ -367,18 +424,7 @@ class XdebugHandler
|
||||
array_push($php, '-n', '-c', $this->tmpIni);
|
||||
}
|
||||
|
||||
if (defined('STDOUT') && Process::supportsColor(STDOUT)) {
|
||||
$args = Process::addColorOption($args, $this->colorOption);
|
||||
}
|
||||
|
||||
$args = array_merge($php, array($this->script), $args);
|
||||
|
||||
$cmd = Process::escape(array_shift($args), true, true);
|
||||
foreach ($args as $arg) {
|
||||
$cmd .= ' '.Process::escape($arg);
|
||||
}
|
||||
|
||||
return $cmd;
|
||||
return array_merge($php, array($this->script), $args);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -490,7 +536,7 @@ class XdebugHandler
|
||||
/**
|
||||
* Adds restart settings to the environment
|
||||
*
|
||||
* @param string $envArgs
|
||||
* @param string[] $envArgs
|
||||
*/
|
||||
private function setEnvRestartSettings($envArgs)
|
||||
{
|
||||
@@ -542,11 +588,12 @@ class XdebugHandler
|
||||
* Returns true if there are no known configuration issues
|
||||
*
|
||||
* @param string $info Set by method
|
||||
* @return bool
|
||||
*/
|
||||
private function checkConfiguration(&$info)
|
||||
{
|
||||
if (false !== strpos(ini_get('disable_functions'), 'passthru')) {
|
||||
$info = 'passthru function is disabled';
|
||||
if (!function_exists('proc_open')) {
|
||||
$info = 'proc_open function is disabled';
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -560,6 +607,43 @@ class XdebugHandler
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$workingDir = getcwd();
|
||||
if (0 === strpos($workingDir, '\\\\')) {
|
||||
if (defined('PHP_WINDOWS_VERSION_BUILD') && PHP_VERSION_ID < 70400) {
|
||||
$info = 'cmd.exe does not support UNC paths: '.$workingDir;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables async signals and control interrupts in the restarted process
|
||||
*
|
||||
* Available on Unix PHP 7.1+ with the pcntl extension and Windows PHP 7.4+.
|
||||
*/
|
||||
private function tryEnableSignals()
|
||||
{
|
||||
if (function_exists('pcntl_async_signals') && function_exists('pcntl_signal')) {
|
||||
pcntl_async_signals(true);
|
||||
$message = 'Async signals enabled';
|
||||
|
||||
if (!self::$inRestart) {
|
||||
// Restarting, so ignore SIGINT in parent
|
||||
pcntl_signal(SIGINT, SIG_IGN);
|
||||
} elseif (is_int(pcntl_signal_get_handler(SIGINT))) {
|
||||
// Restarted, no handler set so force default action
|
||||
pcntl_signal(SIGINT, SIG_DFL);
|
||||
}
|
||||
}
|
||||
|
||||
if (!self::$inRestart && function_exists('sapi_windows_set_ctrl_handler')) {
|
||||
// Restarting, so set a handler to ignore CTRL events in the parent.
|
||||
// This ensures that CTRL+C events will be available in the child
|
||||
// process without having to enable them there, which is unreliable.
|
||||
sapi_windows_set_ctrl_handler(function ($evt) {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user