Dep: update
주로 PHAN
This commit is contained in:
+15
-4
@@ -49,8 +49,8 @@ class PhpConfig
|
||||
public function usePersistent()
|
||||
{
|
||||
if ($data = $this->getDataAndReset()) {
|
||||
Process::setEnv('PHPRC', $data['tmpIni']);
|
||||
Process::setEnv('PHP_INI_SCAN_DIR', '');
|
||||
$this->updateEnv('PHPRC', $data['tmpIni']);
|
||||
$this->updateEnv('PHP_INI_SCAN_DIR', '');
|
||||
}
|
||||
|
||||
return array();
|
||||
@@ -64,10 +64,21 @@ class PhpConfig
|
||||
private function getDataAndReset()
|
||||
{
|
||||
if ($data = XdebugHandler::getRestartSettings()) {
|
||||
Process::setEnv('PHPRC', $data['phprc']);
|
||||
Process::setEnv('PHP_INI_SCAN_DIR', $data['scanDir']);
|
||||
$this->updateEnv('PHPRC', $data['phprc']);
|
||||
$this->updateEnv('PHP_INI_SCAN_DIR', $data['scanDir']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a restart settings value in the environment
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|false $value
|
||||
*/
|
||||
private function updateEnv($name, $value)
|
||||
{
|
||||
Process::setEnv($name, false !== $value ? $value : null);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-79
@@ -12,59 +12,12 @@
|
||||
namespace Composer\XdebugHandler;
|
||||
|
||||
/**
|
||||
* Provides utility functions to prepare a child process command-line and set
|
||||
* environment variables in that process.
|
||||
* Process utility functions
|
||||
*
|
||||
* @author John Stevenson <john-stevenson@blueyonder.co.uk>
|
||||
* @internal
|
||||
*/
|
||||
class Process
|
||||
{
|
||||
/**
|
||||
* Returns an array of parameters, including a color option if required
|
||||
*
|
||||
* A color option is needed because child process output is piped.
|
||||
*
|
||||
* @param array $args The script parameters
|
||||
* @param string $colorOption The long option to force color output
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function addColorOption(array $args, $colorOption)
|
||||
{
|
||||
if (!$colorOption
|
||||
|| in_array($colorOption, $args)
|
||||
|| !preg_match('/^--([a-z]+$)|(^--[a-z]+=)/', $colorOption, $matches)) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
if (isset($matches[2])) {
|
||||
// Handle --color(s)= options
|
||||
if (false !== ($index = array_search($matches[2].'auto', $args))) {
|
||||
$args[$index] = $colorOption;
|
||||
return $args;
|
||||
} elseif (preg_grep('/^'.$matches[2].'/', $args)) {
|
||||
return $args;
|
||||
}
|
||||
} elseif (in_array('--no-'.$matches[1], $args)) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
// Check for NO_COLOR variable (https://no-color.org/)
|
||||
if (false !== getenv('NO_COLOR')) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
if (false !== ($index = array_search('--', $args))) {
|
||||
// Position option before double-dash delimiter
|
||||
array_splice($args, $index, 0, $colorOption);
|
||||
} else {
|
||||
$args[] = $colorOption;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string to be used as a shell argument.
|
||||
*
|
||||
@@ -109,53 +62,33 @@ class Process
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the output stream supports colors
|
||||
* Escapes an array of arguments that make up a shell command
|
||||
*
|
||||
* This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo
|
||||
* terminals via named pipes, so we can only check the environment.
|
||||
* @param array $args Argument list, with the module name first
|
||||
*
|
||||
* @param mixed $output A valid CLI output stream
|
||||
*
|
||||
* @return bool
|
||||
* @return string The escaped command line
|
||||
*/
|
||||
public static function supportsColor($output)
|
||||
public static function escapeShellCommand(array $args)
|
||||
{
|
||||
if ('Hyper' === getenv('TERM_PROGRAM')) {
|
||||
return true;
|
||||
$cmd = self::escape(array_shift($args), true, true);
|
||||
foreach ($args as $arg) {
|
||||
$cmd .= ' '.self::escape($arg);
|
||||
}
|
||||
|
||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
||||
return (function_exists('sapi_windows_vt100_support')
|
||||
&& sapi_windows_vt100_support($output))
|
||||
|| false !== getenv('ANSICON')
|
||||
|| 'ON' === getenv('ConEmuANSI')
|
||||
|| 'xterm' === getenv('TERM');
|
||||
}
|
||||
|
||||
if (function_exists('stream_isatty')) {
|
||||
return stream_isatty($output);
|
||||
}
|
||||
|
||||
if (function_exists('posix_isatty')) {
|
||||
return posix_isatty($output);
|
||||
}
|
||||
|
||||
$stat = fstat($output);
|
||||
// Check if formatted mode is S_IFCHR
|
||||
return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
|
||||
return $cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes putenv environment changes available in $_SERVER and $_ENV
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|false $value A false value unsets the variable
|
||||
* @param string|null $value A null value unsets the variable
|
||||
*
|
||||
* @return bool Whether the environment variable was set
|
||||
*/
|
||||
public static function setEnv($name, $value = false)
|
||||
public static function setEnv($name, $value = null)
|
||||
{
|
||||
$unset = false === $value;
|
||||
$unset = null === $value;
|
||||
|
||||
if (!putenv($unset ? $name : $name.'='.$value)) {
|
||||
return false;
|
||||
|
||||
+9
-3
@@ -33,6 +33,7 @@ class Status
|
||||
private $envAllowXdebug;
|
||||
private $loaded;
|
||||
private $logger;
|
||||
private $modeOff;
|
||||
private $time;
|
||||
|
||||
/**
|
||||
@@ -91,7 +92,12 @@ class Status
|
||||
|
||||
private function reportCheck($loaded)
|
||||
{
|
||||
$this->loaded = $loaded;
|
||||
list($version, $mode) = explode('|', $loaded);
|
||||
|
||||
if ($version) {
|
||||
$this->loaded = '('.$version.')'.($mode ? ' mode='.$mode : '');
|
||||
}
|
||||
$this->modeOff = $mode === 'off';
|
||||
$this->output('Checking '.$this->envAllowXdebug);
|
||||
}
|
||||
|
||||
@@ -112,7 +118,7 @@ class Status
|
||||
if ($this->loaded) {
|
||||
$text = sprintf('No restart (%s)', $this->getEnvAllow());
|
||||
if (!getenv($this->envAllowXdebug)) {
|
||||
$text .= ' Allowed by application';
|
||||
$text .= ' Allowed by '.($this->modeOff ? 'mode' : 'application');
|
||||
}
|
||||
$this->output($text);
|
||||
}
|
||||
@@ -157,7 +163,7 @@ class Status
|
||||
*/
|
||||
private function getLoadedMessage()
|
||||
{
|
||||
$loaded = $this->loaded ? sprintf('loaded (%s)', $this->loaded) : 'not loaded';
|
||||
$loaded = $this->loaded ? sprintf('loaded %s', $this->loaded) : 'not loaded';
|
||||
return 'The Xdebug extension is '.$loaded;
|
||||
}
|
||||
}
|
||||
|
||||
+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