From 7e793e6c7471b063cbad0d847a3fbb89b352d200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Fri, 9 Mar 2018 19:23:35 +0000 Subject: [PATCH 1/4] Restart PHP process with the right settings Check for the PHP configuration when starting box and restart the process with xdebug disabled and `phar.readonly` turned off. Closes #5 --- bin/box | 9 +- box.json.dist | 56 ----- composer.json | 3 +- composer.lock | 57 ++++- src/Console/Application.php | 15 ++ src/Php/PhpSettingsHandler.php | 418 +++++++++++++++++++++++++++++++++ src/Php/Process.php | 129 ++++++++++ src/Php/Status.php | 142 +++++++++++ 8 files changed, 765 insertions(+), 64 deletions(-) create mode 100644 src/Php/PhpSettingsHandler.php create mode 100644 src/Php/Process.php create mode 100644 src/Php/Status.php diff --git a/bin/box b/bin/box index b2afaac83..80c70aeb4 100755 --- a/bin/box +++ b/bin/box @@ -17,7 +17,9 @@ namespace KevinGH\Box; use ErrorException; use KevinGH\Box\Console\Application; +use KevinGH\Box\Php\PhpSettingsHandler; use RuntimeException; +use Symfony\Component\Console\Logger\ConsoleLogger; $findAutoloader = function () { if (file_exists($autoload = __DIR__.'/../../../autoload.php')) { @@ -67,5 +69,10 @@ set_error_handler( } ); +[$input, $output] = Application::createIO(); +$logger = new ConsoleLogger($output); + +(new PhpSettingsHandler('box', '--ansi', $logger))->check(); + $app = new Application(); -$app->run(); +$app->run($input, $output); diff --git a/box.json.dist b/box.json.dist index ae4a8ea77..95ced80dd 100644 --- a/box.json.dist +++ b/box.json.dist @@ -22,63 +22,7 @@ "res", "src" ], - "finder": [ - { - "notName":[ - "LICENSE*", - "CHANGELOG*", - "CONTRIBUTING*", - "README*", - "UPGRADE*", - "Makefile", - "appveyor*", - "phpdoc*", - "phpunit.xml*", - "phpcs.xml*", - "phpstan.neon*", - "box.json*", - "infection.json*", - "humbug.json*", - "phpbench.json*", - "Vagrantfile", - "composer.*" - ], - "exclude": [ - "bin", - "File", - "mikey179", - "Net", - "phpunit", - "phpunit-test-case", - "Tester", - "Tests", - "tests", - "vendor", - "build", - "fixtures", - "yaml", - "test", - "Test", - "demo", - "doc", - "docs", - "test_old", - "specs", - "dist", - "travis", - ".travis", - "examples" - ], - "in": "vendor" - } - ], - "compression": "GZ", - "compactors": [ - "Herrera\\Box\\Compactor\\Json", - "KevinGH\\Box\\Compactor\\PhpScoper", - "Herrera\\Box\\Compactor\\Php" - ], "git-commit": "git-commit", "git-version": "git-version" } diff --git a/composer.json b/composer.json index f6c9c3504..27371be75 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "amphp/parallel-functions": "^0.1.2", "beberlei/assert": "^2.8", "composer/composer": "^1.6", + "composer/xdebug-handler": "^1.0", "herrera-io/annotations": "~1.0", "humbug/php-scoper": "^1.0@dev", "justinrainbow/json-schema": "^5.2", @@ -37,7 +38,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.2", - "infection/infection": "^0.7.0", + "infection/infection": "^0.8", "mikey179/vfsStream": "^1.1", "phpunit/phpunit": "^7.0", "symfony/var-dumper": "^3.4 || ^4.0" diff --git a/composer.lock b/composer.lock index a6ed38b5c..8886ea1a9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "7b62c2f70427bda1b34fed79f902e602", + "content-hash": "ebf5cfb2fa80a86a1362d061487de46e", "packages": [ { "name": "amphp/amp", @@ -722,6 +722,50 @@ ], "time": "2018-01-31T13:17:27+00:00" }, + { + "name": "composer/xdebug-handler", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "1852e549ad0d6f2910f89c27fec833dda1b25b4a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/1852e549ad0d6f2910f89c27fec833dda1b25b4a", + "reference": "1852e549ad0d6f2910f89c27fec833dda1b25b4a", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "time": "2018-03-08T13:09:50+00:00" + }, { "name": "doctrine/annotations", "version": "v1.6.0", @@ -2432,16 +2476,16 @@ }, { "name": "infection/infection", - "version": "0.7.1", + "version": "0.8.1", "source": { "type": "git", "url": "https://github.com/infection/infection.git", - "reference": "44751a5835ec44e7f2754ddcf21a2012f8219c23" + "reference": "0f8109caa95552c3661c6847b8ea2a8a362e3103" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/infection/infection/zipball/44751a5835ec44e7f2754ddcf21a2012f8219c23", - "reference": "44751a5835ec44e7f2754ddcf21a2012f8219c23", + "url": "https://api.github.com/repos/infection/infection/zipball/0f8109caa95552c3661c6847b8ea2a8a362e3103", + "reference": "0f8109caa95552c3661c6847b8ea2a8a362e3103", "shasum": "" }, "require": { @@ -2451,6 +2495,7 @@ "pimple/pimple": "^3.2", "sebastian/diff": "^1.4 || ^2.0 || ^3.0", "symfony/console": "^3.2 || ^4.0", + "symfony/filesystem": "^3.2 || ^4.0", "symfony/finder": "^3.2 || ^4.0", "symfony/process": "^3.2 || ^4.0", "symfony/yaml": "^3.2 || ^4.0" @@ -2491,7 +2536,7 @@ "testing", "unit testing" ], - "time": "2018-02-02T11:25:42+00:00" + "time": "2018-03-01T06:23:00+00:00" }, { "name": "mikey179/vfsStream", diff --git a/src/Console/Application.php b/src/Console/Application.php index a1944682c..1a4e8c8f8 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -19,6 +19,10 @@ use KevinGH\Box\Console\Command\SelfUpdateCommand; use Symfony\Component\Console\Application as SymfonyApplication; use Symfony\Component\Console\Helper\HelperSet; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\OutputInterface; final class Application extends SymfonyApplication { @@ -42,6 +46,17 @@ public function __construct(string $name = 'Box', string $version = '@git-versio parent::__construct($name, $version); } + public static function createIO(): array + { + $input = new ArgvInput(); + $output = new ConsoleOutput(); + + // TODO: see if this could not be made static & public... + (new self())->configureIO($input, $output); + + return [$input, $output]; + } + /** * {@inheritdoc} */ diff --git a/src/Php/PhpSettingsHandler.php b/src/Php/PhpSettingsHandler.php new file mode 100644 index 000000000..90a0255ee --- /dev/null +++ b/src/Php/PhpSettingsHandler.php @@ -0,0 +1,418 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace KevinGH\Box\Php; + +use function file_put_contents; +use Psr\Log\LoggerInterface; +use RuntimeException; + +/** + * @author John Stevenson + * @author Théo Fidry + */ +class PhpSettingsHandler +{ + // MODIFIED + const SUFFIX_ALLOW = '_ALLOW_INITIAL_SETTINGS'; + // --MODIFIED + const SUFFIX_INIS = '_ORIGINAL_INIS'; + const RESTART_ID = 'internal'; + + private static $name; + private static $skipped; + + private $envPrefix; + private $cli; + private $colorOption; + // MODIFIED: varaible name + private $envAllowIniSettings; + // --MODIFIED + private $envOriginalInis; + private $restart; + private $statusWriter; + private $tmpIni; + + /** + * Constructor + * + * The $envPrefix is used to create distinct environment variables. It is + * uppercased and prepended to the default base values. For example 'myapp' + * 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 + * @param LoggerInterface $logger Activates status message output to a PSR-3 logger + * + * @throws RuntimeException If a parameter is invalid + */ + final public function __construct($envPrefix, $colorOption = '', LoggerInterface $logger = null) + { + // MODIFIED: function made final + logger + if (!is_string($envPrefix) || empty($envPrefix) || !is_string($colorOption)) { + throw new RuntimeException('Invalid constructor parameter'); + } + + self::$name = strtoupper($envPrefix); + + $this->envPrefix = $envPrefix; + $this->envAllowIniSettings = self::$name.self::SUFFIX_ALLOW; + $this->envOriginalInis = self::$name.self::SUFFIX_INIS; + + $this->colorOption = $colorOption; + $this->cli = PHP_SAPI === 'cli'; + + // MODIFIED + // renamed $this->loaded to $this->restart + $this->restart = $this->shouldRestart(); + + if (null !== $logger) { + $this->setLogger($logger); + } + // -- MODIFIED + } + + + /** + * @return bool + */ + protected function shouldRestart() + { + return extension_loaded('xdebug') || in_array(ini_get('phar.readonly'), ['1', 'On'], true); + } + + /** + * @return array All current ini settings + */ + protected function getIniSettings() + { + $loaded = ini_get_all(null, false); + $loaded['phar.readonly'] = '0'; + + return $loaded; + } + + /** + * Returns true if the tmp ini file was written + * + * The filename is passed as the -c option when the process restarts. + * + * @param array $iniFiles All ini files used in the current process + * + * @return bool + */ + private function writeTmpIni(array $iniFiles) + { + // MODIFIED + if (!$this->tmpIni = tempnam(sys_get_temp_dir(), $this->envPrefix.'_')) { + // --MODIFIED + return false; + } + + // $iniFiles has at least one item and it may be empty + if (empty($iniFiles[0])) { + array_shift($iniFiles); + } + + $content = ''; + $regex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi'; + + foreach ($iniFiles as $file) { + $data = preg_replace($regex, ';$1', file_get_contents($file)); + $content .= $data.PHP_EOL; + } + + // MODIFIED + $loaded = $this->getIniSettings(); + // --MODIFIED + $config = parse_ini_string($content); + $content .= $this->mergeLoadedConfig($loaded, $config); + + // Work-around for https://bugs.php.net/bug.php?id=75932 + $content .= 'opcache.enable_cli=0'.PHP_EOL; + + file_put_contents('tmp-php.ini', $content); + + return @file_put_contents($this->tmpIni, $content); + } + + /** + * Returns the restart command line + * + * @param array $args The argv array + * + * @return string + */ + private function getCommand(array $args) + { + if (defined('STDOUT') && Process::supportsColor(STDOUT)) { + $args = Process::addColorOption($args, $this->colorOption); + } + + if (!file_exists($args[0])) { + // If we are auto-prepended stdin can be used. + $args[0] = '--'; + $this->notify(Status::INFO, 'Directly executed code is being used'); + } + + $args = array_merge(array(PHP_BINARY, '-c', $this->tmpIni), $args); + + $cmd = Process::escape(array_shift($args), true, true); + foreach ($args as $arg) { + $cmd .= ' '.Process::escape($arg); + } + + // MODIFIED + $this->notify(Status::INFO, "Restart with: \"$cmd\""); + // --MODIFIED + + return $cmd; + } + + /** + * Activates status message output to a PSR3 logger + * + * @param LoggerInterface $logger + */ + public function setLogger(LoggerInterface $logger) + { + $this->statusWriter = new Status($logger, $this->restart, $this->envAllowIniSettings); + } + + /** + * Checks if xdebug is loaded and the process needs to be restarted + * + * If so, then a tmp ini is created with the xdebug ini entry commented out. + * If scanned inis have been loaded, these are combined into the tmp ini + * and PHP_INI_SCAN_DIR is set to an empty value. Current ini locations are + * are stored in MYAPP_ORIGINAL_INIS (where 'MYAPP' is the prefix passed in the + * constructor) for use in the restarted process. + * + * This behaviour can be disabled by setting the MYAPP_ALLOW_XDEBUG + * environment variable to 1. This variable is used internally so that the + * restarted process is created only once and PHP_INI_SCAN_DIR can be + * restored to its original value. + */ + public function check() + { + $this->notify(Status::CHECK); + $envArgs = explode('|', strval(getenv($this->envAllowIniSettings)), 4); + + if ($this->restart && empty($envArgs[0])) { + // Restart required + $this->notify(Status::RESTART); + + if ($this->prepareRestart()) { + $command = $this->getCommand($_SERVER['argv']); + $this->notify(Status::RESTARTING); + $this->restart($command); + } + return; + } + + if (self::RESTART_ID === $envArgs[0] && count($envArgs) >= 3) { + // Restarting, so unset environment variable and extract saved values + $this->notify(Status::RESTARTED); + + putenv($this->envAllowIniSettings); + $version = $envArgs[1]; + $scannedInis = $envArgs[2]; + + if (!$this->restart) { + // Version is only set if restart is successful + self::$skipped = $version; + } + + if ($scannedInis) { + // Scan dir will have been changed, so restore it + if (isset($envArgs[3])) { + putenv('PHP_INI_SCAN_DIR='.$envArgs[3]); + } else { + putenv('PHP_INI_SCAN_DIR'); + } + } + return; + } + + $this->notify(Status::NORESTART); + } + + /** + * Returns an array of php.ini locations with at least one entry + * + * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files. + * The loaded ini location is the first entry and may be empty. + * + * @return array + */ + public static function getAllIniFiles() + { + if (!empty(self::$name)) { + $env = getenv(self::$name.self::SUFFIX_INIS); + + if (false !== $env) { + return explode(PATH_SEPARATOR, $env); + } + } + + $paths = array(strval(php_ini_loaded_file())); + + if ($scanned = php_ini_scanned_files()) { + $paths = array_merge($paths, array_map('trim', explode(',', $scanned))); + } + + return $paths; + } + + /** + * Returns the xdebug version that triggered a successful restart + * + * @return string + */ + public static function getSkippedVersion() + { + return strval(self::$skipped); + } + + /** + * Executes the restarted command then deletes the tmp ini + * + * @param string $command + */ + protected function restart($command) + { + passthru($command, $exitCode); + $this->notify(Status::INFO, sprintf('Restarted process exited: %d', $exitCode)); + + if (!empty($this->tmpIni)) { + @unlink($this->tmpIni); + } + + exit($exitCode); + } + + /** + * Returns true if everything was written for the restart + * + * If any of the following fails (however unlikely) we must return false to + * stop potential recursion: + * - tmp ini file creation + * - environment variable creation + * + * @return bool + */ + private function prepareRestart() + { + $error = ''; + $iniFiles = self::getAllIniFiles(); + $scannedInis = count($iniFiles) > 1; + $scanDir = getenv('PHP_INI_SCAN_DIR'); + + if (!$this->cli) { + $error = 'Unsupported SAPI: '.PHP_SAPI; + } elseif (!defined('PHP_BINARY')) { + $error = 'PHP version is too old: '.PHP_VERSION; + } elseif (!$this->writeTmpIni($iniFiles)) { + $error = 'Unable to create temporary ini file'; + } elseif (!$this->setEnvironment($scannedInis, $scanDir, $iniFiles)) { + $error = 'Unable to set environment variables'; + } + + if ($error) { + $this->notify(Status::ERROR, $error); + } + + return empty($error); + } + + /** + * Returns true if the restart environment variables were set + * + * @param bool $scannedInis Whether there were scanned ini files + * @param false|string $scanDir PHP_INI_SCAN_DIR environment variable + * @param array $iniFiles All ini files used in the current process + * + * @return bool + */ + private function setEnvironment($scannedInis, $scanDir, array $iniFiles) + { + // Set scan dir env to an empty value if there were scanned ini files + if ($scannedInis && !putenv('PHP_INI_SCAN_DIR=')) { + return false; + } + + // Make original inis available to restarted process + if (!putenv($this->envOriginalInis.'='.implode(PATH_SEPARATOR, $iniFiles))) { + return false; + } + + // Flag restarted process and save values for it to use + $envArgs = array( + self::RESTART_ID, + $this->restart, + intval($scannedInis), + ); + + if ($scannedInis && false !== $scanDir) { + // Only add original scan dir if it was set + $envArgs[] = $scanDir; + } + + return putenv($this->envAllowIniSettings.'='.implode('|', $envArgs)); + } + + /** + * Logs status messages + * + * @param string $op Status handler constant + * @param null|string $data Optional data + */ + private function notify($op, $data = null) + { + if ($this->statusWriter) { + $this->statusWriter->report($op, $data); + } + } + + /** + * Returns default or changed settings for the tmp ini + * + * Ini settings can be passed on the command line using the -d option. To + * preserve these, all loaded settings that are either not present or + * different from those in the ini files are added at the end of the tmp ini. + * + * @param array $loadedConfig All current ini settings + * @param array $iniConfig Settings from user ini files + * + * @return string + */ + private function mergeLoadedConfig(array $loadedConfig, array $iniConfig) + { + $content = ''; + + foreach ($loadedConfig as $name => $value) { + // Values will either be null, string or array (HHVM only) + if (!is_string($value) || strpos($name, 'xdebug') === 0) { + continue; + } + + if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) { + // Based on main -d option handling in php-src/sapi/cli/php_cli.c + if ($value && !ctype_alnum($value)) { + $value = '"'.str_replace('"', '\\"', $value).'"'; + } + $content .= $name.'='.$value.PHP_EOL; + } + } + + return $content; + } +} diff --git a/src/Php/Process.php b/src/Php/Process.php new file mode 100644 index 000000000..ec43f192d --- /dev/null +++ b/src/Php/Process.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace KevinGH\Box\Php; + +/** + * Provides utility functions to prepare a child process command-line. + * + * @author John Stevenson + */ +class Process +{ + /** + * Returns the process arguments, appending a color option if required + * + * A color option is needed because child process output is piped. + * + * @param array $args The argv array + * @param $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. Note args[0] is the script name + if ($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; + } + + $args[] = $colorOption; + return $args; + } + + + /** + * Escapes a string to be used as a shell argument. + * + * From https://github.com/johnstevenson/winbox-args + * MIT Licensed (c) John Stevenson + * + * @param string $arg The argument to be escaped + * @param bool $meta Additionally escape cmd.exe meta characters + * @param bool $module The argument is the module to invoke + * + * @return string The escaped argument + */ + public static function escape($arg, $meta = true, $module = false) + { + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + return escapeshellarg($arg); + } + + $quote = strpbrk($arg, " \t") !== false || $arg === ''; + $arg = preg_replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes); + + if ($meta) { + $meta = $dquotes || preg_match('/%[^%]+%/', $arg); + + if (!$meta) { + $quote = $quote || strpbrk($arg, '^&|<>()') !== false; + } elseif ($module && !$dquotes && $quote) { + $meta = false; + } + } + + if ($quote) { + $arg = preg_replace('/(\\\\*)$/', '$1$1', $arg); + $arg = '"'.$arg.'"'; + } + + if ($meta) { + $arg = preg_replace('/(["^&|<>()%])/', '^$1', $arg); + } + + return $arg; + } + + /** + * Returns true if the output stream supports colors + * + * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo + * terminals via named pipes, so we can only check the environment. + * + * @param mixed $output A valid CLI output stream + * + * @return bool + */ + public static function supportsColor($output) + { + 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); + } elseif (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; + } +} diff --git a/src/Php/Status.php b/src/Php/Status.php new file mode 100644 index 000000000..bbd3b24b7 --- /dev/null +++ b/src/Php/Status.php @@ -0,0 +1,142 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace KevinGH\Box\Php; + +use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; + +/** + * @author John Stevenson + */ +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'; + + private $envAllowXdebug; + private $loaded; + private $logger; + private $time; + + /** + * Constructor + * + * @param LoggerInterface $logger + * @param string $loaded The loaded xdebug version + * @param string $envAllowXdebug Prefixed _ALLOW_XDEBUG name + */ + public function __construct(LoggerInterface $logger, $loaded, $envAllowXdebug) + { + $start = getenv(self::ENV_RESTART); + putenv(self::ENV_RESTART); + $this->time = $start ? round((microtime(true) - $start) * 1000) : 0; + + $this->logger = $logger; + $this->loaded = $loaded; + $this->envAllowXdebug = $envAllowXdebug; + } + + /** + * Calls a handler method to report a message + * + * @param string $op The handler constant + * @param null|string $data Data required by the handler + */ + public function report($op, $data) + { + $func = array($this, 'report'.$op); + call_user_func($func, $data); + } + + /** + * Sends a status message to the logger + * + * @param string $text + * @param string $level + */ + private function output($text, $level = null) + { + $this->logger->log($level ?: LogLevel::DEBUG, $text); + } + + private function reportCheck() + { + $this->output('Checking '.$this->envAllowXdebug); + } + + private function reportError($error) + { + $this->output(sprintf("No restart (%s)", $error), LogLevel::WARNING); + } + + private function reportInfo($info) + { + $this->output($info); + } + + private function reportNoRestart() + { + $this->output($this->getLoadedMessage()); + + if ($this->loaded) { + $text = sprintf("No restart (%s)", $this->getEnvAllow()); + $this->output($text); + } + } + + private function reportRestart() + { + $this->output($this->getLoadedMessage()); + putenv(self::ENV_RESTART.'='.strval(microtime(true))); + } + + private function reportRestarted() + { + $loaded = $this->getLoadedMessage(); + $text = sprintf('Restarted (%d ms). %s', $this->time, $loaded); + $level = $this->loaded ? LogLevel::WARNING : null; + $this->output($text, $level); + } + + private function reportRestarting() + { + $text = sprintf("Process restarting (%s)", $this->getEnvAllow()); + $this->output($text); + } + + /** + * Returns the _ALLOW_XDEBUG environment variable as name=value + * + * @return string + */ + private function getEnvAllow() + { + return $this->envAllowXdebug.'='.getenv($this->envAllowXdebug); + } + + /** + * Returns the xdebug status and version + * + * @return string + */ + private function getLoadedMessage() + { + $loaded = $this->loaded ? sprintf('loaded (%s)', $this->loaded) : 'not loaded'; + return 'The xdebug extension is '.$loaded; + } +} From c40139a06b479fda051316ca73b3ab7e392506ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Sat, 7 Apr 2018 21:33:03 +0200 Subject: [PATCH 2/4] Update the code thanks to the new changes made in Composer XdebugHandler --- bin/box | 9 +- composer.json | 2 +- composer.lock | 13 +- src/Console/Application.php | 15 -- src/Console/Command/Compile.php | 5 + src/Php/PhpSettingsHandler.php | 418 -------------------------------- src/Php/Process.php | 129 ---------- src/Php/Status.php | 142 ----------- src/PhpSettingsHandler.php | 65 +++++ 9 files changed, 79 insertions(+), 719 deletions(-) delete mode 100644 src/Php/PhpSettingsHandler.php delete mode 100644 src/Php/Process.php delete mode 100644 src/Php/Status.php create mode 100644 src/PhpSettingsHandler.php diff --git a/bin/box b/bin/box index 80c70aeb4..b2afaac83 100755 --- a/bin/box +++ b/bin/box @@ -17,9 +17,7 @@ namespace KevinGH\Box; use ErrorException; use KevinGH\Box\Console\Application; -use KevinGH\Box\Php\PhpSettingsHandler; use RuntimeException; -use Symfony\Component\Console\Logger\ConsoleLogger; $findAutoloader = function () { if (file_exists($autoload = __DIR__.'/../../../autoload.php')) { @@ -69,10 +67,5 @@ set_error_handler( } ); -[$input, $output] = Application::createIO(); -$logger = new ConsoleLogger($output); - -(new PhpSettingsHandler('box', '--ansi', $logger))->check(); - $app = new Application(); -$app->run($input, $output); +$app->run(); diff --git a/composer.json b/composer.json index 27371be75..a05c024ed 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "amphp/parallel-functions": "^0.1.2", "beberlei/assert": "^2.8", "composer/composer": "^1.6", - "composer/xdebug-handler": "^1.0", + "composer/xdebug-handler": "dev-master", "herrera-io/annotations": "~1.0", "humbug/php-scoper": "^1.0@dev", "justinrainbow/json-schema": "^5.2", diff --git a/composer.lock b/composer.lock index 8886ea1a9..331572a13 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "ebf5cfb2fa80a86a1362d061487de46e", + "content-hash": "e140cc7b922032aaf61ed62dc1bd7500", "packages": [ { "name": "amphp/amp", @@ -724,16 +724,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.0.0", + "version": "dev-master", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "1852e549ad0d6f2910f89c27fec833dda1b25b4a" + "reference": "e5d43e216d0008b60dc4addbe616a1df48778eea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/1852e549ad0d6f2910f89c27fec833dda1b25b4a", - "reference": "1852e549ad0d6f2910f89c27fec833dda1b25b4a", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/e5d43e216d0008b60dc4addbe616a1df48778eea", + "reference": "e5d43e216d0008b60dc4addbe616a1df48778eea", "shasum": "" }, "require": { @@ -764,7 +764,7 @@ "Xdebug", "performance" ], - "time": "2018-03-08T13:09:50+00:00" + "time": "2018-04-04T17:39:46+00:00" }, { "name": "doctrine/annotations", @@ -4067,6 +4067,7 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { + "composer/xdebug-handler": 20, "humbug/php-scoper": 20 }, "prefer-stable": false, diff --git a/src/Console/Application.php b/src/Console/Application.php index 1a4e8c8f8..a1944682c 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -19,10 +19,6 @@ use KevinGH\Box\Console\Command\SelfUpdateCommand; use Symfony\Component\Console\Application as SymfonyApplication; use Symfony\Component\Console\Helper\HelperSet; -use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Output\OutputInterface; final class Application extends SymfonyApplication { @@ -46,17 +42,6 @@ public function __construct(string $name = 'Box', string $version = '@git-versio parent::__construct($name, $version); } - public static function createIO(): array - { - $input = new ArgvInput(); - $output = new ConsoleOutput(); - - // TODO: see if this could not be made static & public... - (new self())->configureIO($input, $output); - - return [$input, $output]; - } - /** * {@inheritdoc} */ diff --git a/src/Console/Command/Compile.php b/src/Console/Command/Compile.php index af95bed0c..a3ac9025f 100644 --- a/src/Console/Command/Compile.php +++ b/src/Console/Command/Compile.php @@ -16,16 +16,19 @@ use Amp\MultiReasonException; use Assert\Assertion; +use function ini_get; use KevinGH\Box\Box; use KevinGH\Box\Compactor; use KevinGH\Box\Configuration; use KevinGH\Box\Console\Logger\BuildLogger; use KevinGH\Box\MapFile; +use KevinGH\Box\PhpSettingsHandler; use KevinGH\Box\StubGenerator; use RuntimeException; use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Logger\ConsoleLogger; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\Question; use Symfony\Component\Console\Style\SymfonyStyle; @@ -96,6 +99,8 @@ protected function configure(): void */ protected function execute(InputInterface $input, OutputInterface $output): void { + (new PhpSettingsHandler(new ConsoleLogger($output)))->check(); + if (true === $input->getOption(self::DEBUG_OPTION)) { enable_debug(); } diff --git a/src/Php/PhpSettingsHandler.php b/src/Php/PhpSettingsHandler.php deleted file mode 100644 index 90a0255ee..000000000 --- a/src/Php/PhpSettingsHandler.php +++ /dev/null @@ -1,418 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace KevinGH\Box\Php; - -use function file_put_contents; -use Psr\Log\LoggerInterface; -use RuntimeException; - -/** - * @author John Stevenson - * @author Théo Fidry - */ -class PhpSettingsHandler -{ - // MODIFIED - const SUFFIX_ALLOW = '_ALLOW_INITIAL_SETTINGS'; - // --MODIFIED - const SUFFIX_INIS = '_ORIGINAL_INIS'; - const RESTART_ID = 'internal'; - - private static $name; - private static $skipped; - - private $envPrefix; - private $cli; - private $colorOption; - // MODIFIED: varaible name - private $envAllowIniSettings; - // --MODIFIED - private $envOriginalInis; - private $restart; - private $statusWriter; - private $tmpIni; - - /** - * Constructor - * - * The $envPrefix is used to create distinct environment variables. It is - * uppercased and prepended to the default base values. For example 'myapp' - * 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 - * @param LoggerInterface $logger Activates status message output to a PSR-3 logger - * - * @throws RuntimeException If a parameter is invalid - */ - final public function __construct($envPrefix, $colorOption = '', LoggerInterface $logger = null) - { - // MODIFIED: function made final + logger - if (!is_string($envPrefix) || empty($envPrefix) || !is_string($colorOption)) { - throw new RuntimeException('Invalid constructor parameter'); - } - - self::$name = strtoupper($envPrefix); - - $this->envPrefix = $envPrefix; - $this->envAllowIniSettings = self::$name.self::SUFFIX_ALLOW; - $this->envOriginalInis = self::$name.self::SUFFIX_INIS; - - $this->colorOption = $colorOption; - $this->cli = PHP_SAPI === 'cli'; - - // MODIFIED - // renamed $this->loaded to $this->restart - $this->restart = $this->shouldRestart(); - - if (null !== $logger) { - $this->setLogger($logger); - } - // -- MODIFIED - } - - - /** - * @return bool - */ - protected function shouldRestart() - { - return extension_loaded('xdebug') || in_array(ini_get('phar.readonly'), ['1', 'On'], true); - } - - /** - * @return array All current ini settings - */ - protected function getIniSettings() - { - $loaded = ini_get_all(null, false); - $loaded['phar.readonly'] = '0'; - - return $loaded; - } - - /** - * Returns true if the tmp ini file was written - * - * The filename is passed as the -c option when the process restarts. - * - * @param array $iniFiles All ini files used in the current process - * - * @return bool - */ - private function writeTmpIni(array $iniFiles) - { - // MODIFIED - if (!$this->tmpIni = tempnam(sys_get_temp_dir(), $this->envPrefix.'_')) { - // --MODIFIED - return false; - } - - // $iniFiles has at least one item and it may be empty - if (empty($iniFiles[0])) { - array_shift($iniFiles); - } - - $content = ''; - $regex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi'; - - foreach ($iniFiles as $file) { - $data = preg_replace($regex, ';$1', file_get_contents($file)); - $content .= $data.PHP_EOL; - } - - // MODIFIED - $loaded = $this->getIniSettings(); - // --MODIFIED - $config = parse_ini_string($content); - $content .= $this->mergeLoadedConfig($loaded, $config); - - // Work-around for https://bugs.php.net/bug.php?id=75932 - $content .= 'opcache.enable_cli=0'.PHP_EOL; - - file_put_contents('tmp-php.ini', $content); - - return @file_put_contents($this->tmpIni, $content); - } - - /** - * Returns the restart command line - * - * @param array $args The argv array - * - * @return string - */ - private function getCommand(array $args) - { - if (defined('STDOUT') && Process::supportsColor(STDOUT)) { - $args = Process::addColorOption($args, $this->colorOption); - } - - if (!file_exists($args[0])) { - // If we are auto-prepended stdin can be used. - $args[0] = '--'; - $this->notify(Status::INFO, 'Directly executed code is being used'); - } - - $args = array_merge(array(PHP_BINARY, '-c', $this->tmpIni), $args); - - $cmd = Process::escape(array_shift($args), true, true); - foreach ($args as $arg) { - $cmd .= ' '.Process::escape($arg); - } - - // MODIFIED - $this->notify(Status::INFO, "Restart with: \"$cmd\""); - // --MODIFIED - - return $cmd; - } - - /** - * Activates status message output to a PSR3 logger - * - * @param LoggerInterface $logger - */ - public function setLogger(LoggerInterface $logger) - { - $this->statusWriter = new Status($logger, $this->restart, $this->envAllowIniSettings); - } - - /** - * Checks if xdebug is loaded and the process needs to be restarted - * - * If so, then a tmp ini is created with the xdebug ini entry commented out. - * If scanned inis have been loaded, these are combined into the tmp ini - * and PHP_INI_SCAN_DIR is set to an empty value. Current ini locations are - * are stored in MYAPP_ORIGINAL_INIS (where 'MYAPP' is the prefix passed in the - * constructor) for use in the restarted process. - * - * This behaviour can be disabled by setting the MYAPP_ALLOW_XDEBUG - * environment variable to 1. This variable is used internally so that the - * restarted process is created only once and PHP_INI_SCAN_DIR can be - * restored to its original value. - */ - public function check() - { - $this->notify(Status::CHECK); - $envArgs = explode('|', strval(getenv($this->envAllowIniSettings)), 4); - - if ($this->restart && empty($envArgs[0])) { - // Restart required - $this->notify(Status::RESTART); - - if ($this->prepareRestart()) { - $command = $this->getCommand($_SERVER['argv']); - $this->notify(Status::RESTARTING); - $this->restart($command); - } - return; - } - - if (self::RESTART_ID === $envArgs[0] && count($envArgs) >= 3) { - // Restarting, so unset environment variable and extract saved values - $this->notify(Status::RESTARTED); - - putenv($this->envAllowIniSettings); - $version = $envArgs[1]; - $scannedInis = $envArgs[2]; - - if (!$this->restart) { - // Version is only set if restart is successful - self::$skipped = $version; - } - - if ($scannedInis) { - // Scan dir will have been changed, so restore it - if (isset($envArgs[3])) { - putenv('PHP_INI_SCAN_DIR='.$envArgs[3]); - } else { - putenv('PHP_INI_SCAN_DIR'); - } - } - return; - } - - $this->notify(Status::NORESTART); - } - - /** - * Returns an array of php.ini locations with at least one entry - * - * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files. - * The loaded ini location is the first entry and may be empty. - * - * @return array - */ - public static function getAllIniFiles() - { - if (!empty(self::$name)) { - $env = getenv(self::$name.self::SUFFIX_INIS); - - if (false !== $env) { - return explode(PATH_SEPARATOR, $env); - } - } - - $paths = array(strval(php_ini_loaded_file())); - - if ($scanned = php_ini_scanned_files()) { - $paths = array_merge($paths, array_map('trim', explode(',', $scanned))); - } - - return $paths; - } - - /** - * Returns the xdebug version that triggered a successful restart - * - * @return string - */ - public static function getSkippedVersion() - { - return strval(self::$skipped); - } - - /** - * Executes the restarted command then deletes the tmp ini - * - * @param string $command - */ - protected function restart($command) - { - passthru($command, $exitCode); - $this->notify(Status::INFO, sprintf('Restarted process exited: %d', $exitCode)); - - if (!empty($this->tmpIni)) { - @unlink($this->tmpIni); - } - - exit($exitCode); - } - - /** - * Returns true if everything was written for the restart - * - * If any of the following fails (however unlikely) we must return false to - * stop potential recursion: - * - tmp ini file creation - * - environment variable creation - * - * @return bool - */ - private function prepareRestart() - { - $error = ''; - $iniFiles = self::getAllIniFiles(); - $scannedInis = count($iniFiles) > 1; - $scanDir = getenv('PHP_INI_SCAN_DIR'); - - if (!$this->cli) { - $error = 'Unsupported SAPI: '.PHP_SAPI; - } elseif (!defined('PHP_BINARY')) { - $error = 'PHP version is too old: '.PHP_VERSION; - } elseif (!$this->writeTmpIni($iniFiles)) { - $error = 'Unable to create temporary ini file'; - } elseif (!$this->setEnvironment($scannedInis, $scanDir, $iniFiles)) { - $error = 'Unable to set environment variables'; - } - - if ($error) { - $this->notify(Status::ERROR, $error); - } - - return empty($error); - } - - /** - * Returns true if the restart environment variables were set - * - * @param bool $scannedInis Whether there were scanned ini files - * @param false|string $scanDir PHP_INI_SCAN_DIR environment variable - * @param array $iniFiles All ini files used in the current process - * - * @return bool - */ - private function setEnvironment($scannedInis, $scanDir, array $iniFiles) - { - // Set scan dir env to an empty value if there were scanned ini files - if ($scannedInis && !putenv('PHP_INI_SCAN_DIR=')) { - return false; - } - - // Make original inis available to restarted process - if (!putenv($this->envOriginalInis.'='.implode(PATH_SEPARATOR, $iniFiles))) { - return false; - } - - // Flag restarted process and save values for it to use - $envArgs = array( - self::RESTART_ID, - $this->restart, - intval($scannedInis), - ); - - if ($scannedInis && false !== $scanDir) { - // Only add original scan dir if it was set - $envArgs[] = $scanDir; - } - - return putenv($this->envAllowIniSettings.'='.implode('|', $envArgs)); - } - - /** - * Logs status messages - * - * @param string $op Status handler constant - * @param null|string $data Optional data - */ - private function notify($op, $data = null) - { - if ($this->statusWriter) { - $this->statusWriter->report($op, $data); - } - } - - /** - * Returns default or changed settings for the tmp ini - * - * Ini settings can be passed on the command line using the -d option. To - * preserve these, all loaded settings that are either not present or - * different from those in the ini files are added at the end of the tmp ini. - * - * @param array $loadedConfig All current ini settings - * @param array $iniConfig Settings from user ini files - * - * @return string - */ - private function mergeLoadedConfig(array $loadedConfig, array $iniConfig) - { - $content = ''; - - foreach ($loadedConfig as $name => $value) { - // Values will either be null, string or array (HHVM only) - if (!is_string($value) || strpos($name, 'xdebug') === 0) { - continue; - } - - if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) { - // Based on main -d option handling in php-src/sapi/cli/php_cli.c - if ($value && !ctype_alnum($value)) { - $value = '"'.str_replace('"', '\\"', $value).'"'; - } - $content .= $name.'='.$value.PHP_EOL; - } - } - - return $content; - } -} diff --git a/src/Php/Process.php b/src/Php/Process.php deleted file mode 100644 index ec43f192d..000000000 --- a/src/Php/Process.php +++ /dev/null @@ -1,129 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace KevinGH\Box\Php; - -/** - * Provides utility functions to prepare a child process command-line. - * - * @author John Stevenson - */ -class Process -{ - /** - * Returns the process arguments, appending a color option if required - * - * A color option is needed because child process output is piped. - * - * @param array $args The argv array - * @param $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. Note args[0] is the script name - if ($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; - } - - $args[] = $colorOption; - return $args; - } - - - /** - * Escapes a string to be used as a shell argument. - * - * From https://github.com/johnstevenson/winbox-args - * MIT Licensed (c) John Stevenson - * - * @param string $arg The argument to be escaped - * @param bool $meta Additionally escape cmd.exe meta characters - * @param bool $module The argument is the module to invoke - * - * @return string The escaped argument - */ - public static function escape($arg, $meta = true, $module = false) - { - if (!defined('PHP_WINDOWS_VERSION_BUILD')) { - return escapeshellarg($arg); - } - - $quote = strpbrk($arg, " \t") !== false || $arg === ''; - $arg = preg_replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes); - - if ($meta) { - $meta = $dquotes || preg_match('/%[^%]+%/', $arg); - - if (!$meta) { - $quote = $quote || strpbrk($arg, '^&|<>()') !== false; - } elseif ($module && !$dquotes && $quote) { - $meta = false; - } - } - - if ($quote) { - $arg = preg_replace('/(\\\\*)$/', '$1$1', $arg); - $arg = '"'.$arg.'"'; - } - - if ($meta) { - $arg = preg_replace('/(["^&|<>()%])/', '^$1', $arg); - } - - return $arg; - } - - /** - * Returns true if the output stream supports colors - * - * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo - * terminals via named pipes, so we can only check the environment. - * - * @param mixed $output A valid CLI output stream - * - * @return bool - */ - public static function supportsColor($output) - { - 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); - } elseif (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; - } -} diff --git a/src/Php/Status.php b/src/Php/Status.php deleted file mode 100644 index bbd3b24b7..000000000 --- a/src/Php/Status.php +++ /dev/null @@ -1,142 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace KevinGH\Box\Php; - -use Psr\Log\LoggerInterface; -use Psr\Log\LogLevel; - -/** - * @author John Stevenson - */ -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'; - - private $envAllowXdebug; - private $loaded; - private $logger; - private $time; - - /** - * Constructor - * - * @param LoggerInterface $logger - * @param string $loaded The loaded xdebug version - * @param string $envAllowXdebug Prefixed _ALLOW_XDEBUG name - */ - public function __construct(LoggerInterface $logger, $loaded, $envAllowXdebug) - { - $start = getenv(self::ENV_RESTART); - putenv(self::ENV_RESTART); - $this->time = $start ? round((microtime(true) - $start) * 1000) : 0; - - $this->logger = $logger; - $this->loaded = $loaded; - $this->envAllowXdebug = $envAllowXdebug; - } - - /** - * Calls a handler method to report a message - * - * @param string $op The handler constant - * @param null|string $data Data required by the handler - */ - public function report($op, $data) - { - $func = array($this, 'report'.$op); - call_user_func($func, $data); - } - - /** - * Sends a status message to the logger - * - * @param string $text - * @param string $level - */ - private function output($text, $level = null) - { - $this->logger->log($level ?: LogLevel::DEBUG, $text); - } - - private function reportCheck() - { - $this->output('Checking '.$this->envAllowXdebug); - } - - private function reportError($error) - { - $this->output(sprintf("No restart (%s)", $error), LogLevel::WARNING); - } - - private function reportInfo($info) - { - $this->output($info); - } - - private function reportNoRestart() - { - $this->output($this->getLoadedMessage()); - - if ($this->loaded) { - $text = sprintf("No restart (%s)", $this->getEnvAllow()); - $this->output($text); - } - } - - private function reportRestart() - { - $this->output($this->getLoadedMessage()); - putenv(self::ENV_RESTART.'='.strval(microtime(true))); - } - - private function reportRestarted() - { - $loaded = $this->getLoadedMessage(); - $text = sprintf('Restarted (%d ms). %s', $this->time, $loaded); - $level = $this->loaded ? LogLevel::WARNING : null; - $this->output($text, $level); - } - - private function reportRestarting() - { - $text = sprintf("Process restarting (%s)", $this->getEnvAllow()); - $this->output($text); - } - - /** - * Returns the _ALLOW_XDEBUG environment variable as name=value - * - * @return string - */ - private function getEnvAllow() - { - return $this->envAllowXdebug.'='.getenv($this->envAllowXdebug); - } - - /** - * Returns the xdebug status and version - * - * @return string - */ - private function getLoadedMessage() - { - $loaded = $this->loaded ? sprintf('loaded (%s)', $this->loaded) : 'not loaded'; - return 'The xdebug extension is '.$loaded; - } -} diff --git a/src/PhpSettingsHandler.php b/src/PhpSettingsHandler.php new file mode 100644 index 000000000..8227eee80 --- /dev/null +++ b/src/PhpSettingsHandler.php @@ -0,0 +1,65 @@ + + * Théo Fidry + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace KevinGH\Box; + +use Composer\XdebugHandler\XdebugHandler; +use Psr\Log\LoggerInterface; +use Symfony\Component\Filesystem\Exception\IOException; +use const FILE_APPEND; +use const PHP_EOL; +use function file_put_contents; + +final class PhpSettingsHandler extends XdebugHandler +{ + private $required; + + /** + * {@inheritdoc} + */ + public function __construct(LoggerInterface $logger) + { + parent::__construct('box', '--ansi'); + + $this->setLogger($logger); + + $this->required = (bool) ini_get('phar.readonly'); + } + + /** + * {@inheritdoc} + */ + protected function requiresRestart($isLoaded) + { + return $this->required || $isLoaded; + } + + protected function restart($command): void + { + if ($this->required) { + if (false === @file_put_contents($this->tmpIni, 'phar.readonly=0'.PHP_EOL, FILE_APPEND)) { + throw new IOException( + sprintf('Failed to write file "%s".', $this->tmpIni), + 0, + null, + $this->tmpIni + ); + } + + // TODO: log that phar.readonly was appended: https://github.com/composer/xdebug-handler/pull/51 + } + + parent::restart($command); + } +} From 68a9f01d925237f467e8b4230a81958f7ab947dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Sat, 7 Apr 2018 21:45:20 +0200 Subject: [PATCH 3/4] Fix tests --- box.json.dist | 56 ++++++++++++++++++++++++++++++++++++++ phpunit.xml.dist | 4 +++ src/PhpSettingsHandler.php | 3 ++ 3 files changed, 63 insertions(+) diff --git a/box.json.dist b/box.json.dist index 95ced80dd..ae4a8ea77 100644 --- a/box.json.dist +++ b/box.json.dist @@ -22,7 +22,63 @@ "res", "src" ], + "finder": [ + { + "notName":[ + "LICENSE*", + "CHANGELOG*", + "CONTRIBUTING*", + "README*", + "UPGRADE*", + "Makefile", + "appveyor*", + "phpdoc*", + "phpunit.xml*", + "phpcs.xml*", + "phpstan.neon*", + "box.json*", + "infection.json*", + "humbug.json*", + "phpbench.json*", + "Vagrantfile", + "composer.*" + ], + "exclude": [ + "bin", + "File", + "mikey179", + "Net", + "phpunit", + "phpunit-test-case", + "Tester", + "Tests", + "tests", + "vendor", + "build", + "fixtures", + "yaml", + "test", + "Test", + "demo", + "doc", + "docs", + "test_old", + "specs", + "dist", + "travis", + ".travis", + "examples" + ], + "in": "vendor" + } + ], + "compression": "GZ", + "compactors": [ + "Herrera\\Box\\Compactor\\Json", + "KevinGH\\Box\\Compactor\\PhpScoper", + "Herrera\\Box\\Compactor\\Php" + ], "git-commit": "git-commit", "git-version": "git-version" } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b354f7f8d..eb7a1ec3f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -4,6 +4,10 @@ bootstrap="tests/bootstrap.php" colors="true"> + + + + tests/ diff --git a/src/PhpSettingsHandler.php b/src/PhpSettingsHandler.php index 8227eee80..750283f52 100644 --- a/src/PhpSettingsHandler.php +++ b/src/PhpSettingsHandler.php @@ -45,6 +45,9 @@ protected function requiresRestart($isLoaded) return $this->required || $isLoaded; } + /** + * {@inheritdoc} + */ protected function restart($command): void { if ($this->required) { From 1de79d6a291ea8a83664f736efa124897341d14b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= Date: Sat, 7 Apr 2018 21:54:00 +0200 Subject: [PATCH 4/4] Add logging --- src/PhpSettingsHandler.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PhpSettingsHandler.php b/src/PhpSettingsHandler.php index 750283f52..3a0d785f7 100644 --- a/src/PhpSettingsHandler.php +++ b/src/PhpSettingsHandler.php @@ -23,6 +23,7 @@ final class PhpSettingsHandler extends XdebugHandler { + private $logger; private $required; /** @@ -33,6 +34,7 @@ public function __construct(LoggerInterface $logger) parent::__construct('box', '--ansi'); $this->setLogger($logger); + $this->logger = $logger; $this->required = (bool) ini_get('phar.readonly'); } @@ -60,7 +62,7 @@ protected function restart($command): void ); } - // TODO: log that phar.readonly was appended: https://github.com/composer/xdebug-handler/pull/51 + $this->logger->debug('Configured `phar.readonly=0`'); } parent::restart($command);